abstract class UpdateInfoService : Service

Known direct subclasses
ListenableFutureUpdateInfoService

ListenableFuture-based compatibility wrapper around UpdateInfoService's suspending APIs.


Base class for implementing the AndroidX Security State Update Provider service.

This abstract class provides the foundational implementation for the IUpdateInfoService AIDL interface. It manages the complexity of serving security update information to client applications, enforcing a consistent contract for data freshness, resource usage, and observability.

Responsibilities

  • Session Management: Implements a Factory pattern to create secure, stateful sessions (IUpdateInfoSession) for each connecting client.

  • Identity Verification: Validates client-provided package names against their Kernel-level UIDs to prevent Intent spoofing and guarantee secure attribution. To successfully perform this validation, the host application must have broad package visibility (e.g., via the QUERY_ALL_PACKAGES permission or by running as the system user).

  • Concurrency Control: Implements Double-Checked Locking to prevent "thundering herd" scenarios where multiple clients trigger parallel network requests.

  • Rate Limiting: Enforces a default throttling policy (e.g., maximum one check per hour) to protect backend infrastructure.

  • Caching: Automatically persists fetched results and freshness metadata to local storage.

Monitoring & Telemetry

To support high-scale hosts that require strict monitoring, this class provides a comprehensive set of observability hooks. Hosts can override these methods to integrate with their own logging infrastructure:

Host Implementation

Host applications (such as the System Updater, Google Play Store, or OEM-specific updaters) written in Kotlin should extend this class and implement fetchUpdates to provide the actual network logic. Java consumers should instead extend ListenableFutureUpdateInfoService.

Manifest Declaration

To expose this service, you must declare it in your AndroidManifest.xml with the exported="true" attribute and an intent-filter for the UPDATE_INFO_SERVICE action:

<service
android:name=".MyUpdateInfoService"
android:exported="true">
<intent-filter>
<action android:name="androidx.security.state.provider.UPDATE_INFO_SERVICE" />
</intent-filter>
</service>

Summary

Public constructors

Public functions

open Unit
dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array<String>?)

Dumps the state of the service for debugging (e.g. adb shell dumpsys activity service).

final IBinder?
onBind(intent: Intent?)

Called by the system when a client binds to the service.

final Boolean
onUnbind(intent: Intent?)

Called by the system when all clients have disconnected from the service's factory interface.

Protected functions

abstract suspend List<UpdateInfo>

Performs the actual network request to fetch fresh updates from the backend.

open Int

Retrieves the UID of the calling process.

open Unit
onClientConnected(packageName: String, callerUid: Int)

Called when a client successfully establishes a session with the update provider.

open Unit
onClientDisconnected(packageName: String, callerUid: Int)

Called when a client explicitly closes its session or its process terminates unexpectedly.

open Unit

Callback for handling exceptions that occur during the update check process.

open Unit

Called when a request completes.

open Boolean

Determines if the local cache is stale and a refresh should be attempted.

open Boolean

Checks if the update check operation should be throttled.

Inherited functions

From android.content.Context
Boolean
bindIsolatedService(
    service: Intent,
    flags: Context.BindServiceFlags,
    instanceName: String,
    executor: Executor,
    conn: ServiceConnection
)
Int
ColorStateList
Drawable?
String
getString(resId: Int)
String
getString(resId: Int, vararg formatArgs: Any)
T
<T : Any> getSystemService(serviceClass: Class<T>)
CharSequence
getText(resId: Int)
TypedArray
TypedArray
TypedArray
TypedArray
obtainStyledAttributes(
    set: AttributeSet?,
    attrs: IntArray,
    defStyleAttr: Int,
    defStyleRes: Int
)
Unit
Unit
sendBroadcastWithMultiplePermissions(
    intent: Intent,
    receiverPermissions: Array<String>
)
From android.content.ContextWrapper
Boolean
bindIsolatedService(
    service: Intent,
    flags: Int,
    instanceName: String,
    executor: Executor,
    conn: ServiceConnection
)
Boolean
bindService(
    service: Intent,
    conn: ServiceConnection,
    flags: Context.BindServiceFlags
)
Boolean
bindService(service: Intent, conn: ServiceConnection, flags: Int)
Boolean
bindService(
    service: Intent,
    flags: Context.BindServiceFlags,
    executor: Executor,
    conn: ServiceConnection
)
Boolean
bindService(
    service: Intent,
    flags: Int,
    executor: Executor,
    conn: ServiceConnection
)
Boolean
bindServiceAsUser(
    service: Intent,
    conn: ServiceConnection,
    flags: Context.BindServiceFlags,
    user: UserHandle
)
Boolean
bindServiceAsUser(
    service: Intent,
    conn: ServiceConnection,
    flags: Int,
    user: UserHandle
)
Int
Int
IntArray
Int
Int
checkCallingUriPermission(uri: Uri, modeFlags: Int)
IntArray
Int
checkContentUriPermissionFull(uri: Uri, pid: Int, uid: Int, modeFlags: Int)
Int
checkPermission(permission: String, pid: Int, uid: Int)
Int
Int
checkUriPermission(uri: Uri, pid: Int, uid: Int, modeFlags: Int)
Int
checkUriPermission(
    uri: Uri?,
    readPermission: String?,
    writePermission: String?,
    pid: Int,
    uid: Int,
    modeFlags: Int
)
IntArray
checkUriPermissions(
    uris: MutableList<Uri>,
    pid: Int,
    uid: Int,
    modeFlags: Int
)
Unit
Context
createAttributionContext(attributionTag: String?)
Context
createConfigurationContext(overrideConfiguration: Configuration)
Context
createContext(contextParams: ContextParams)
Context
Context
Context
Context
Context
createPackageContext(packageName: String, flags: Int)
Context
createWindowContext(type: Int, options: Bundle?)
Context
createWindowContext(display: Display, type: Int, options: Bundle?)
Array<String>
Boolean
Boolean
Boolean
Unit
enforceCallingOrSelfPermission(permission: String, message: String?)
Unit
enforceCallingOrSelfUriPermission(
    uri: Uri,
    modeFlags: Int,
    message: String
)
Unit
enforceCallingPermission(permission: String, message: String?)
Unit
enforceCallingUriPermission(uri: Uri, modeFlags: Int, message: String)
Unit
enforcePermission(permission: String, pid: Int, uid: Int, message: String?)
Unit
enforceUriPermission(
    uri: Uri,
    pid: Int,
    uid: Int,
    modeFlags: Int,
    message: String
)
Unit
enforceUriPermission(
    uri: Uri?,
    readPermission: String?,
    writePermission: String?,
    pid: Int,
    uid: Int,
    modeFlags: Int,
    message: String?
)
Array<String>
File
File
getDir(name: String, mode: Int)
File?
Array<File>
File
SharedPreferences
Any
String?
getSystemServiceName(serviceClass: Class<*>)
Unit
grantUriPermission(toPackage: String, uri: Uri, modeFlags: Int)
Boolean
moveDatabaseFrom(sourceContext: Context, name: String)
Boolean
moveSharedPreferencesFrom(sourceContext: Context, name: String)
FileInputStream
FileOutputStream
openFileOutput(name: String, mode: Int)
SQLiteDatabase
openOrCreateDatabase(
    name: String,
    mode: Int,
    factory: SQLiteDatabase.CursorFactory
)
SQLiteDatabase
openOrCreateDatabase(
    name: String,
    mode: Int,
    factory: SQLiteDatabase.CursorFactory,
    errorHandler: DatabaseErrorHandler?
)
Drawable
Unit
Unit
Unit
Intent?
Intent?
registerReceiver(
    receiver: BroadcastReceiver?,
    filter: IntentFilter,
    flags: Int
)
Intent?
registerReceiver(
    receiver: BroadcastReceiver?,
    filter: IntentFilter,
    broadcastPermission: String?,
    scheduler: Handler?
)
Intent?
registerReceiver(
    receiver: BroadcastReceiver?,
    filter: IntentFilter,
    broadcastPermission: String?,
    scheduler: Handler?,
    flags: Int
)
Unit
Unit
Unit
Unit
revokeUriPermission(uri: Uri, modeFlags: Int)
Unit
revokeUriPermission(targetPackage: String, uri: Uri, modeFlags: Int)
Unit
Unit
sendBroadcast(intent: Intent, receiverPermission: String?)
Unit
sendBroadcast(intent: Intent, receiverPermission: String?, options: Bundle?)
Unit
Unit
sendBroadcastAsUser(
    intent: Intent,
    user: UserHandle,
    receiverPermission: String?
)
Unit
sendOrderedBroadcast(intent: Intent, receiverPermission: String?)
Unit
sendOrderedBroadcast(
    intent: Intent,
    receiverPermission: String?,
    options: Bundle?
)
Unit
sendOrderedBroadcast(
    intent: Intent,
    receiverPermission: String?,
    resultReceiver: BroadcastReceiver?,
    scheduler: Handler?,
    initialCode: Int,
    initialData: String?,
    initialExtras: Bundle?
)
Unit
sendOrderedBroadcast(
    intent: Intent,
    receiverPermission: String?,
    options: Bundle?,
    resultReceiver: BroadcastReceiver?,
    scheduler: Handler?,
    initialCode: Int,
    initialData: String?,
    initialExtras: Bundle?
)
Unit
sendOrderedBroadcast(
    intent: Intent,
    receiverPermission: String?,
    receiverAppOp: String?,
    resultReceiver: BroadcastReceiver?,
    scheduler: Handler?,
    initialCode: Int,
    initialData: String?,
    initialExtras: Bundle?
)
Unit
sendOrderedBroadcast(
    intent: Intent,
    initialCode: Int,
    receiverPermission: String?,
    receiverAppOp: String?,
    resultReceiver: BroadcastReceiver?,
    scheduler: Handler?,
    initialData: String?,
    initialExtras: Bundle?,
    options: Bundle?
)
Unit
sendOrderedBroadcastAsUser(
    intent: Intent,
    user: UserHandle,
    receiverPermission: String?,
    resultReceiver: BroadcastReceiver?,
    scheduler: Handler?,
    initialCode: Int,
    initialData: String?,
    initialExtras: Bundle?
)
Unit
Unit
sendStickyBroadcast(intent: Intent, options: Bundle?)
Unit
Unit
sendStickyOrderedBroadcast(
    intent: Intent,
    resultReceiver: BroadcastReceiver?,
    scheduler: Handler?,
    initialCode: Int,
    initialData: String?,
    initialExtras: Bundle?
)
Unit
sendStickyOrderedBroadcastAsUser(
    intent: Intent,
    user: UserHandle,
    resultReceiver: BroadcastReceiver?,
    scheduler: Handler?,
    initialCode: Int,
    initialData: String?,
    initialExtras: Bundle?
)
Unit
setTheme(resid: Int)
Unit
Unit
Unit
Unit
startActivities(intents: Array<Intent>, options: Bundle?)
Unit
Unit
startActivity(intent: Intent, options: Bundle?)
ComponentName?
Boolean
startInstrumentation(
    className: ComponentName,
    profileFile: String?,
    arguments: Bundle?
)
Unit
startIntentSender(
    intent: IntentSender,
    fillInIntent: Intent?,
    flagsMask: Int,
    flagsValues: Int,
    extraFlags: Int
)
Unit
startIntentSender(
    intent: IntentSender,
    fillInIntent: Intent?,
    flagsMask: Int,
    flagsValues: Int,
    extraFlags: Int,
    options: Bundle?
)
ComponentName?
startService(service: Intent)
Boolean
Unit
Unit
Unit
Unit
Unit
Unit
updateServiceGroup(conn: ServiceConnection, group: Int, importance: Int)
From android.app.Service
Unit
Unit
Unit
Unit
Unit
Unit
onRebind(intent: Intent)
Unit
onStart(intent: Intent, startId: Int)
Int
onStartCommand(intent: Intent, flags: Int, startId: Int)
Unit
onTaskRemoved(rootIntent: Intent)
Unit
onTimeout(startId: Int)
Unit
onTimeout(startId: Int, fgsType: Int)
Unit
onTrimMemory(level: Int)
Unit
startForeground(id: Int, notification: Notification)
Unit
startForeground(
    id: Int,
    notification: Notification,
    foregroundServiceType: Int
)
Unit
stopForeground(notificationBehavior: Int)
Unit
stopForeground(removeNotification: Boolean)
Unit
Unit
stopSelf(startId: Int)
Boolean
stopSelfResult(startId: Int)

Public constructors

UpdateInfoService

Added in 1.0.0-beta01
UpdateInfoService()

Public functions

dump

Added in 1.0.0-beta01
open fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array<String>?): Unit

Dumps the state of the service for debugging (e.g. adb shell dumpsys activity service).

This implementation provides a snapshot of the internal state, including:

  • Active Request Count: To detect stuck threads or high concurrency.

  • Global Last Check Time: The timestamp of the last successful service-wide sync (Service Health).

  • Throttling Status: Whether the rate limiter is currently blocking network requests.

  • Cached Updates: A complete list of stored updates with detailed metadata (Component, SPL, Provider, Published Date, Last Checked Time).

onBind

Added in 1.0.0-beta01
final fun onBind(intent: Intent?): IBinder?

Called by the system when a client binds to the service.

This method verifies that the Intent action matches the expected contract (ACTION_BIND). If the action is missing or incorrect, the binding is rejected to ensure the service is not exposed unintentionally.

Upon successful verification, this method returns a factory Binder (IUpdateInfoService) that allows the client to establish a secure, authenticated session.

Parameters
intent: Intent?

The Intent that was used to bind to this service.

Returns
IBinder?

The IUpdateInfoService factory interface, or null if the Intent action is invalid.

onUnbind

Added in 1.0.0-beta01
final fun onUnbind(intent: Intent?): Boolean

Called by the system when all clients have disconnected from the service's factory interface.

This method is marked final to enforce the library's lifecycle contract. It returns false, which forces the Android system to call onBind for any future connections. This simplifies the service's lifecycle management by ensuring we never need to handle the android.app.Service.onRebind edge case.

Lifecycle Note: Individual client session disconnections and telemetry are tracked when the client calls IUpdateInfoSession.close (which triggers onClientDisconnected), not within this system-level callback.

Parameters
intent: Intent?

The Intent that was used to bind to this service.

Returns
Boolean

false to ensure onRebind is not called.

Protected functions

fetchUpdates

protected abstract suspend fun fetchUpdates(): List<UpdateInfo>

Performs the actual network request to fetch fresh updates from the backend.

Template Method: This method is implemented by the host application (e.g., the System Updater) and invoked by the UpdateInfoService base class on a background thread. The base class handles concurrency, caching, and rate-limiting.

Preconditions: This method is only called if shouldFetchUpdates returns true and shouldThrottle returns false (the rate limiter allows the request).

Error Handling: If this method throws an exception (e.g., due to a network timeout or server error), the base class will catch it, invoke onFetchFailed for telemetry logging, and gracefully return the currently cached data to the client.

Returns
List<UpdateInfo>

A list of UpdateInfo objects currently available for the device.

getCallerUid

Added in 1.0.0-beta01
protected open fun getCallerUid(): Int

Retrieves the UID of the calling process.

The default implementation delegates to android.os.Binder.getCallingUid. Host implementations using a proxy or broker architecture should override this to return the logical client UID instead of the broker's UID, ensuring correct attribution.

onClientConnected

Added in 1.0.0-beta01
protected open fun onClientConnected(packageName: String, callerUid: Int): Unit

Called when a client successfully establishes a session with the update provider.

This method is invoked when a client calls IUpdateInfoService.openSession and passes the identity validation checks.

Usage: Override this method to log connection metrics (e.g., "Session Started") or track adoption trends based on the client package name.

Client Identity & Security: The packageName provided to this hook is strictly validated against the Kernel-verified callerUid by the Android system. This guarantees that the identity is authentic and cannot be spoofed.

Package Visibility: The host application must have package visibility (e.g., via the QUERY_ALL_PACKAGES permission) to verify the client's package name. If the host cannot verify the package name, the connection will be rejected with a SecurityException before this hook is ever called.

Parameters
packageName: String

The explicitly verified package name of the client application.

callerUid: Int

The Kernel-verified Linux UID of the client process.

onClientDisconnected

Added in 1.0.0-beta01
protected open fun onClientDisconnected(packageName: String, callerUid: Int): Unit

Called when a client explicitly closes its session or its process terminates unexpectedly.

This method is invoked when the client calls IUpdateInfoSession.close or when the Android system detects that the process hosting the client has died (via android.os.IBinder.DeathRecipient).

Usage: Override this method to log session ends (e.g., "Session Ended") to calculate session duration when paired with onClientConnected. You can also use this to perform per-client resource cleanup.

Parameters
packageName: String

The explicitly verified package name of the client application.

callerUid: Int

The Kernel-verified Linux UID of the client process.

onFetchFailed

Added in 1.0.0-beta01
protected open fun onFetchFailed(e: Exception): Unit

Callback for handling exceptions that occur during the update check process.

Template Method: The UpdateInfoService base class executes the update check logic and catches all unhandled exceptions to prevent the service from crashing. When an exception is caught, the base class gracefully falls back to returning the currently cached data to the client, and invokes this method.

This method will be triggered if an exception occurs during any step of the lifecycle, including:

  • Determining cache freshness (e.g., inside shouldFetchUpdates).

  • Evaluating rate limits (e.g., inside shouldThrottle).

  • Fetching data from the backend (e.g., inside fetchUpdates).

  • Persisting the fetched results or metadata to local storage.

The default implementation logs the error to Logcat. Hosts can override this method to report failures to their own telemetry or crash reporting systems.

Parameters
e: Exception

The exception caught by the base class during the operation.

onRequestCompleted

Added in 1.0.0-beta01
protected open fun onRequestCompleted(telemetry: UpdateCheckTelemetry): Unit

Called when a request completes. Override this to log telemetry.

This hook is called for every request to listAvailableUpdates, regardless of whether it succeeded, failed, was throttled, or was served from the cache.

Usage: Use this hook to log granular performance metrics (latency histograms) and resource usage.

Attribution: The telemetry object contains the UpdateCheckTelemetry.callerUid, which is the Kernel-verified UID of the calling process. This is the authoritative source for attributing load (CPU/Network/Lock Contention) to specific client applications.

Parameters
telemetry: UpdateCheckTelemetry

The UpdateCheckTelemetry containing metrics and outcomes.

shouldFetchUpdates

Added in 1.0.0-beta01
protected open fun shouldFetchUpdates(): Boolean

Determines if the local cache is stale and a refresh should be attempted.

The default implementation returns true if the data is older than 1 hour. Hosts can override this to implement custom caching policies (e.g., 4 hours, 24 hours).

Returns
Boolean

true if a network fetch should be attempted.

shouldThrottle

Added in 1.0.0-beta01
protected open fun shouldThrottle(): Boolean

Checks if the update check operation should be throttled.

Default Behavior: The default implementation delegates to an internal rate limiter backed by SharedPreferences. This enforces a standard minimum interval (e.g., 1 hour) between network requests to protect backend infrastructure. The rate limiting state persists across application restarts.

Host Customization: Hosts can override this method to inject custom rate-limiting logic, such as blocking background checks when the device is on a metered network or has low battery.

Graceful Degradation: If this method returns true, the base class skips calling fetchUpdates and gracefully returns the currently cached data to the client. Because the client receives the cached state along with its original lastCheckTimeMillis, the client is not expected to manage complex backoff or retry loops.

Returns
Boolean

true if the network request should be blocked (throttled).