Chapter 50: AI, AppFunctions, and Computer Control¶
Android has evolved from a platform that merely runs apps into one that understands them. A constellation of on-device intelligence services now connects user intent to app behavior: the AppFunctions framework lets assistants invoke arbitrary app functionality through a typed RPC contract; Computer Control gives AI agents a virtual display they can tap, swipe, and screenshot; OnDeviceIntelligence runs large language models in an isolated sandbox; and NNAPI exposes hardware accelerators to any native workload. Together with AppSearch, Content Capture, AdServices, and Federated Learning, these subsystems form Android's AI nervous system.
This chapter traces every layer -- from the public SDK class down through AIDL interfaces, into the system_server service implementation, and out to the sandboxed or HAL process on the far side. Every code path is backed by real source files in the current AOSP tree.
50.1 AOSP AI Landscape¶
Before examining any single framework in detail, it helps to see the entire AI / ML surface of AOSP at a glance. The following diagram maps the major subsystems, the process boundaries they cross, and the data flows that connect them.
graph TB
subgraph "App Process"
APP[Third-Party / System App]
AFM[AppFunctionManager]
ODIM[OnDeviceIntelligenceManager]
NNAPI_C["NNAPI C API"]
CCExt[ComputerControlExtensions]
ASM[AppSearchManager]
TCM[TextClassifierManager]
CCM[ContentCaptureManager]
APM[AppPredictionManager]
TM[TopicsManager]
end
subgraph "system_server"
AFMS[AppFunctionManagerServiceImpl]
ODIMS[OnDeviceIntelligenceManagerService]
VDM[VirtualDeviceManager]
CCS_SVC[ComputerControlSession Service]
CCAS[ContentCaptureManagerService]
TCMS[TextClassificationManagerService]
APMS[AppPredictionManagerService]
end
subgraph "Target App Process"
AFS[AppFunctionService]
end
subgraph "Isolated / Sandboxed Process"
ODSIS[OnDeviceSandboxedInferenceService]
ITS[IsolatedTrainingService]
end
subgraph "HAL / Driver Process"
NNHAL["NNAPI HAL (IDevice)"]
ACCEL["GPU / DSP / NPU"]
end
subgraph "Mainline Modules"
APS["AppSearch Module"]
NNM["NeuralNetworks Module"]
ODP["OnDevicePersonalization Module"]
ADS["AdServices Module"]
end
APP --> AFM
APP --> ODIM
APP --> NNAPI_C
APP --> CCExt
APP --> ASM
APP --> TCM
APP --> CCM
APP --> APM
APP --> TM
AFM -- "Binder IPC" --> AFMS
AFMS -- "bindService" --> AFS
ODIM -- "Binder IPC" --> ODIMS
ODIMS -- "isolated bind" --> ODSIS
CCExt -- "Binder IPC" --> VDM
VDM --> CCS_SVC
CCM -- "Binder IPC" --> CCAS
TCM -- "Binder IPC" --> TCMS
APM -- "Binder IPC" --> APMS
ASM -- "Binder IPC" --> APS
NNAPI_C --> NNM
NNM --> NNHAL
NNHAL --> ACCEL
ODP --> ITS
ADS --> TM
50.1.1 Taxonomy of AOSP Intelligence Subsystems¶
| Subsystem | API Level | Module? | Purpose |
|---|---|---|---|
| AppFunctions | 16 (Android 16) | No (framework) | Typed cross-app function invocation |
| Computer Control | 16 (Android 16) | No (framework + extensions lib) | AI-driven UI automation via virtual display |
| OnDeviceIntelligence | 15+ | NeuralNetworks module | Sandboxed LLM / ML inference |
| NNAPI | 8.1+ | NeuralNetworks module | Hardware-accelerated neural network inference |
| AppSearch | 12+ | AppSearch module | On-device full-text search and indexing |
| Content Capture | 10+ | No (framework) | Real-time UI structure capture for intelligence |
| TextClassifier | 8.0+ | No (framework) | Entity recognition, language detection |
| AppPrediction | 10+ | No (framework) | Usage-based app ranking |
| OnDevicePersonalization | 14+ | ODP module | Federated compute, isolated training |
| AdServices | 13+ | AdServices module | Privacy-preserving ad targeting (Topics, FLEDGE) |
50.1.2 Cross-Cutting Design Themes¶
Several architectural themes recur across every AI subsystem:
-
Process isolation. Intelligence services run in isolated or sandboxed processes.
OnDeviceSandboxedInferenceServicedeclaresandroid:isolatedProcess="true".IsolatedTrainingServiceloads TFLite in a separate process. EvenComputerControlSessionoperates through a virtual display that is separated from the default display. -
Typed contracts over open-ended Bundles. AppFunctions uses
GenericDocument(from AppSearch) as its parameter wire format. ODI usesPersistableBundlefor feature/request metadata. Both encourage SDK-level typed wrappers. -
AppSearch as the universal metadata store. App function metadata, app prediction data, and content capture intelligence all converge on AppSearch for indexing and discovery.
-
Permission-gated access with allowlisting. AppFunctions gates execution behind
EXECUTE_APP_FUNCTIONSplus a device-config agent allowlist. Computer Control requiresACCESS_COMPUTER_CONTROL. ODI requiresUSE_ON_DEVICE_INTELLIGENCE. AdServices requiresACCESS_ADSERVICES_TOPICS. -
Cancellation propagation. Nearly every asynchronous API passes an
ICancellationSignaltransport across the Binder boundary, allowing the caller to abort long-running inference or function execution.
50.2 AppFunctions Framework¶
The AppFunctions framework, introduced as a beta feature in Android 16 (2024),
provides a standardized mechanism for AI assistants (agents) to discover and
invoke functionality exposed by arbitrary apps (targets). An assistant can
say "save XYZ into my notes" and the framework routes the request to the
appropriate AppFunctionService implementation without the assistant needing
any compile-time dependency on the note-taking app.
Source tree overview:
frameworks/base/core/java/android/app/appfunctions/
AppFunctionManager.java (973 lines) -- Client-side system service
AppFunctionService.java (224 lines) -- Abstract base for target apps
ExecuteAppFunctionRequest.java (270 lines) -- Request parcelable
ExecuteAppFunctionResponse.java (206 lines) -- Response parcelable
AppFunctionException.java (280 lines) -- Typed error hierarchy
AppFunctionAttribution.java (292 lines) -- Interaction provenance
IAppFunctionManager.aidl (97 lines) -- System server AIDL
IAppFunctionService.aidl (50 lines) -- Target app AIDL (oneway)
IExecuteAppFunctionCallback.aidl -- Async result callback
ICancellationCallback.aidl -- Cancellation transport
...
frameworks/base/services/appfunctions/
java/com/android/server/appfunctions/
AppFunctionManagerServiceImpl.java -- IAppFunctionManager.Stub
RemoteServiceCallerImpl.java -- Service binding logic
CallerValidatorImpl.java -- Permission enforcement
MetadataSyncAdapter.java -- AppSearch metadata sync
AppFunctionAccessHistory.java -- Access audit trail
AppFunctionAgentAllowlistStorage.java -- Agent allowlist
...
50.2.1 Architecture Overview¶
sequenceDiagram
participant Agent as Agent App
participant AFM as AppFunctionManager
participant SS as system_server (AppFunctionManagerServiceImpl)
participant AFS as Target App (AppFunctionService)
Agent->>AFM: executeAppFunction(request, callback)
AFM->>SS: IAppFunctionManager.executeAppFunction(aidlRequest, callback)
Note over SS: Validate permissions, Check agent allowlist, Check enabled state
SS->>AFS: bindService(ACTION AppFunctionService)
SS->>AFS: IAppFunctionService.executeAppFunction(request, callingPackage, signingInfo, cancellationCallback, resultCallback)
AFS-->>AFS: onExecuteFunction(request, callingPackage, signingInfo, cancellationSignal, outcomeReceiver)
AFS->>SS: IExecuteAppFunctionCallback.onSuccess(response)
SS->>AFM: IExecuteAppFunctionCallback.onSuccess(response)
AFM->>Agent: OutcomeReceiver.onResult(response)
50.2.2 The Client: AppFunctionManager¶
AppFunctionManager is registered as a system service under
Context.APP_FUNCTION_SERVICE:
// frameworks/base/core/java/android/app/appfunctions/AppFunctionManager.java
@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
@SystemService(Context.APP_FUNCTION_SERVICE)
public final class AppFunctionManager {
The primary API is executeAppFunction(), which takes four parameters:
// frameworks/base/core/java/android/app/appfunctions/AppFunctionManager.java
@RequiresPermission(value = Manifest.permission.EXECUTE_APP_FUNCTIONS, conditional = true)
@UserHandleAware
public void executeAppFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull @CallbackExecutor Executor executor,
@NonNull CancellationSignal cancellationSignal,
@NonNull OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> callback) {
Internally, the manager wraps the public request into an
ExecuteAppFunctionAidlRequest that adds caller identity and timing:
ExecuteAppFunctionAidlRequest aidlRequest =
new ExecuteAppFunctionAidlRequest(
request,
mContext.getUser(),
mContext.getPackageName(),
/* requestTime= */ SystemClock.elapsedRealtime(),
/* requestWallTime= */ System.currentTimeMillis());
The Binder call returns an ICancellationSignal transport that is
wired back to the caller's CancellationSignal:
ICancellationSignal cancellationTransport =
mService.executeAppFunction(
aidlRequest,
new IExecuteAppFunctionCallback.Stub() {
@Override
public void onSuccess(ExecuteAppFunctionResponse result) {
executor.execute(() -> callback.onResult(result));
}
@Override
public void onError(AppFunctionException exception) {
executor.execute(() -> callback.onError(exception));
}
});
if (cancellationTransport != null) {
cancellationSignal.setRemote(cancellationTransport);
}
50.2.3 Enabled State Management¶
Each app function has a tri-state lifecycle:
| Constant | Value | Meaning |
|---|---|---|
APP_FUNCTION_STATE_DEFAULT |
0 | Reset to the default (typically enabled) |
APP_FUNCTION_STATE_ENABLED |
1 | Explicitly enabled |
APP_FUNCTION_STATE_DISABLED |
2 | Explicitly disabled |
Apps control their own functions via setAppFunctionEnabled():
// frameworks/base/core/java/android/app/appfunctions/AppFunctionManager.java
@UserHandleAware
public void setAppFunctionEnabled(
@NonNull String functionIdentifier,
@EnabledState int newEnabledState,
@NonNull Executor executor,
@NonNull OutcomeReceiver<Void, Exception> callback) {
The enabled state is persisted in AppSearch as an
AppFunctionRuntimeMetadata document, which is separate from the
AppFunctionStaticMetadata that describes the function's schema.
50.2.4 Access Control Model¶
The AppFunctions access model operates on three levels:
graph TD
A[Permission Check] --> B{Has EXECUTE_APP_FUNCTIONS?}
B -->|No| C[ERROR_DENIED]
B -->|Yes| D{Agent in allowlist?}
D -->|No| E[ACCESS_REQUEST_STATE_UNREQUESTABLE]
D -->|Yes| F{Access flags check}
F --> G{User granted?}
G -->|Yes| H[Execute function]
G -->|No| I{Pregranted?}
I -->|Yes| H
I -->|No| J[ACCESS_REQUEST_STATE_DENIED]
Access flags are a bitmask stored per (agent, target) pair:
| Flag | Value | Meaning |
|---|---|---|
ACCESS_FLAG_PREGRANTED |
0x01 | System pre-granted the access |
ACCESS_FLAG_OTHER_GRANTED |
0x02 | Granted via ADB or other mechanism |
ACCESS_FLAG_OTHER_DENIED |
0x04 | Denied via ADB or self-revoke |
ACCESS_FLAG_USER_GRANTED |
0x08 | User explicitly granted via UI |
ACCESS_FLAG_USER_DENIED |
0x10 | User explicitly denied via UI |
The agent allowlist is maintained via DeviceConfig under the
machine_learning namespace with key allowlisted_app_functions_agents,
plus an additional per-device override in
Settings.Secure.APP_FUNCTION_ADDITIONAL_AGENT_ALLOWLIST.
// frameworks/base/services/appfunctions/.../AppFunctionManagerServiceImpl.java
private static final String ALLOWLISTED_APP_FUNCTIONS_AGENTS =
"allowlisted_app_functions_agents";
private static final String NAMESPACE_MACHINE_LEARNING = "machine_learning";
The CallerValidatorImpl class checks both the runtime permission and the
allowlist before any execution proceeds.
50.2.5 The AIDL Interfaces¶
The framework defines two AIDL interfaces -- one facing the client, one facing the target app.
IAppFunctionManager (client-to-system_server):
// frameworks/base/core/java/android/app/appfunctions/IAppFunctionManager.aidl
interface IAppFunctionManager {
ICancellationSignal executeAppFunction(
in ExecuteAppFunctionAidlRequest request,
in IExecuteAppFunctionCallback callback);
void setAppFunctionEnabled(
in String callingPackage,
in String functionIdentifier,
in UserHandle userHandle,
int enabledState,
in IAppFunctionEnabledCallback callback);
int getAccessRequestState(
in String agentPackageName, int agentUserId,
in String targetPackageName, int targetUserId);
int getAccessFlags(...);
boolean updateAccessFlags(...);
void revokeSelfAccess(in String targetPackageName);
List<String> getValidAgents(int userId);
List<String> getValidTargets(int targetUserId);
List<SignedPackageParcel> getAgentAllowlist();
void clearAccessHistory(int userId);
Intent createRequestAccessIntent(in String targetPackageName);
}
IAppFunctionService (system_server-to-target app, oneway):
// frameworks/base/core/java/android/app/appfunctions/IAppFunctionService.aidl
oneway interface IAppFunctionService {
void executeAppFunction(
in ExecuteAppFunctionRequest request,
in String callingPackage,
in android.content.pm.SigningInfo callingPackageSigningInfo,
in ICancellationCallback cancellationCallback,
in IExecuteAppFunctionCallback callback);
}
The oneway modifier is critical: the system_server does not block waiting
for the target app to finish. Results flow back through the
IExecuteAppFunctionCallback.
50.2.6 The Target: AppFunctionService¶
Target apps extend AppFunctionService and implement a single abstract method:
// frameworks/base/core/java/android/app/appfunctions/AppFunctionService.java
@MainThread
public abstract void onExecuteFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull String callingPackage,
@NonNull SigningInfo callingPackageSigningInfo,
@NonNull CancellationSignal cancellationSignal,
@NonNull OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> callback);
The service enforces that only system_server (which holds
BIND_APP_FUNCTION_SERVICE) can call it:
// frameworks/base/core/java/android/app/appfunctions/AppFunctionService.java
if (context.checkCallingPermission(BIND_APP_FUNCTION_SERVICE)
== PERMISSION_DENIED) {
throw new SecurityException("Can only be called by the system server.");
}
The manifest declaration requires the binding permission:
<service android:name=".YourService"
android:permission="android.permission.BIND_APP_FUNCTION_SERVICE">
<intent-filter>
<action android:name="android.app.appfunctions.AppFunctionService" />
</intent-filter>
</service>
50.2.7 Request and Response Wire Format¶
Both ExecuteAppFunctionRequest and ExecuteAppFunctionResponse use
AppSearch's GenericDocument as their parameter wire format. This is not
arbitrary -- it ensures that function parameters can be described by a schema
that AppSearch already knows how to index and query.
Request:
// frameworks/base/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
public final class ExecuteAppFunctionRequest implements Parcelable {
@NonNull private final String mTargetPackageName;
@NonNull private final String mFunctionIdentifier;
@NonNull private final Bundle mExtras;
@NonNull private final GenericDocumentWrapper mParameters;
@Nullable private final AppFunctionAttribution mAttribution;
Response:
// frameworks/base/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
public final class ExecuteAppFunctionResponse implements Parcelable {
public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue";
@NonNull private final GenericDocumentWrapper mResultDocumentWrapper;
@NonNull private final Bundle mExtras;
@NonNull private final List<AppFunctionUriGrant> mUriGrants;
The return value lives at the key PROPERTY_RETURN_VALUE inside the result
GenericDocument. The AppFunction SDK (a separate Jetpack library) provides
typed wrappers that pack/unpack these documents.
50.2.8 Attribution and Audit Trail¶
Every execution can carry an AppFunctionAttribution describing the
interaction that triggered it:
// frameworks/base/core/java/android/app/appfunctions/AppFunctionAttribution.java
public static final int INTERACTION_TYPE_OTHER = 0;
public static final int INTERACTION_TYPE_USER_QUERY = 1;
public static final int INTERACTION_TYPE_USER_SCHEDULED = 2;
The system records these attributions in an access history database
(AppFunctionSQLiteAccessHistory / MultiUserAppFunctionAccessHistory)
exposed through a content provider at:
The access history schema includes:
| Column | Type | Description |
|---|---|---|
agent_package_name |
TEXT | The AI agent that made the call |
target_package_name |
TEXT | The app that was invoked |
interaction_type |
INT | Interaction type constant |
interaction_uri |
TEXT | Link back to interaction context |
thread_id |
TEXT | Groups related function calls |
access_time |
LONG | Timestamp in milliseconds |
access_duration |
LONG | Execution duration in milliseconds |
50.2.9 Error Handling¶
AppFunctionException defines a categorized error code scheme:
// frameworks/base/core/java/android/app/appfunctions/AppFunctionException.java
// Request errors (1000-1999)
public static final int ERROR_DENIED = 1000;
public static final int ERROR_INVALID_ARGUMENT = 1001;
public static final int ERROR_DISABLED = 1002;
public static final int ERROR_FUNCTION_NOT_FOUND = 1003;
// System errors (2000-2999)
public static final int ERROR_SYSTEM_ERROR = 2000;
public static final int ERROR_CANCELLED = 2001;
public static final int ERROR_ENTERPRISE_POLICY_DISALLOWED = 2002;
// App errors (3000-3999)
public static final int ERROR_APP_UNKNOWN_ERROR = 3000;
The getErrorCategory() method maps ranges to categories:
public int getErrorCategory() {
if (mErrorCode >= 1000 && mErrorCode < 2000) return ERROR_CATEGORY_REQUEST_ERROR;
if (mErrorCode >= 2000 && mErrorCode < 3000) return ERROR_CATEGORY_SYSTEM;
if (mErrorCode >= 3000 && mErrorCode < 4000) return ERROR_CATEGORY_APP;
return ERROR_CATEGORY_UNKNOWN;
}
50.2.10 System Server Implementation¶
The service implementation in
frameworks/base/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java
extends IAppFunctionManager.Stub and coordinates:
// frameworks/base/services/appfunctions/.../AppFunctionManagerServiceImpl.java
public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
private final RemoteServiceCaller<IAppFunctionService> mRemoteServiceCaller;
private final CallerValidator mCallerValidator;
private final AppFunctionAccessServiceInterface mAppFunctionAccessService;
private final IUriGrantsManager mUriGrantsManager;
private final MultiUserAppFunctionAccessHistory mMultiUserAppFunctionAccessHistory;
...
Key supporting classes:
| Class | Responsibility |
|---|---|
RemoteServiceCallerImpl |
Binds to target AppFunctionService, manages connection lifecycle |
CallerValidatorImpl |
Enforces EXECUTE_APP_FUNCTIONS, checks allowlist |
MetadataSyncAdapter |
Syncs function metadata to AppSearch on package changes |
AppFunctionPackageMonitor |
Watches for package install/update/remove |
FutureAppSearchSessionImpl |
Async wrapper around AppSearch sessions |
AppFunctionAgentAllowlistStorage |
Persists agent allowlist from DeviceConfig + Settings |
AppFunctionSQLiteAccessHistory |
SQLite backend for the access audit trail |
50.2.11 Function Discovery via AppSearch¶
When a package is installed, updated, or the device boots, the
MetadataSyncAdapter extracts app function metadata from the target app's
AppFunctionService and indexes it as AppFunctionStaticMetadata documents
in AppSearch. Agents discover functions by querying AppSearch:
sequenceDiagram
participant PM as PackageManager
participant MSync as MetadataSyncAdapter
participant AS as AppSearch
PM->>MSync: onPackageChanged(pkg)
MSync->>MSync: Extract static metadata from AppFunctionService
MSync->>AS: PutDocumentsRequest(AppFunctionStaticMetadata)
AS-->>MSync: success
Note over AS: AppFunctionStaticMetadata now queryable by agents with package visibility
50.2.12 SafeOneTimeExecuteAppFunctionCallback¶
A critical defensive wrapper ensures exactly-once delivery:
// frameworks/base/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java
public class SafeOneTimeExecuteAppFunctionCallback {
private final AtomicBoolean mOnResultCalled = new AtomicBoolean(false);
@NonNull private final IExecuteAppFunctionCallback mCallback;
@Nullable private final CompletionCallback mCompletionCallback;
@Nullable private final BeforeCompletionCallback mBeforeCompletionCallback;
private final AtomicLong mExecutionStartTimeAfterBindMillis = new AtomicLong();
public void onResult(@NonNull ExecuteAppFunctionResponse result) {
if (!mOnResultCalled.compareAndSet(false, true)) {
Log.w(TAG, "Ignore subsequent calls to onResult/onError()");
return;
}
try {
if (mBeforeCompletionCallback != null) {
mBeforeCompletionCallback.beforeOnSuccess(result);
}
mCallback.onSuccess(result);
if (mCompletionCallback != null) {
mCompletionCallback.finalizeOnSuccess(
result, mExecutionStartTimeAfterBindMillis.get());
}
} catch (RemoteException ex) {
Log.w(TAG, "Failed to invoke the callback", ex);
}
}
public void onError(@NonNull AppFunctionException error) {
if (!mOnResultCalled.compareAndSet(false, true)) {
Log.w(TAG, "Ignore subsequent calls to onResult/onError()");
return;
}
try {
mCallback.onError(error);
if (mCompletionCallback != null) {
mCompletionCallback.finalizeOnError(
error, mExecutionStartTimeAfterBindMillis.get());
}
} catch (RemoteException ex) {
Log.w(TAG, "Failed to invoke the callback", ex);
}
}
This design pattern is essential because:
-
Target apps might call back multiple times -- The
AppFunctionServiceis third-party code that might erroneously invoke the callback twice. TheAtomicBoolean.compareAndSet()ensures only the first call succeeds. -
RemoteException swallowing -- If the calling process has died by the time the result arrives, the
RemoteExceptionis logged and swallowed rather than crashing the system server. -
Completion hooks -- The
BeforeCompletionCallbackandCompletionCallbackallow the system server to perform actions (like logging, URI grants, and access history recording) around the callback delivery:
public interface CompletionCallback {
void finalizeOnSuccess(
ExecuteAppFunctionResponse result, long executionStartTimeMillis);
void finalizeOnError(
AppFunctionException error, long executionStartTimeMillis);
}
public interface BeforeCompletionCallback {
void beforeOnSuccess(ExecuteAppFunctionResponse result);
}
-
Latency tracking -- The
mExecutionStartTimeAfterBindMillisfield records when execution began after service binding completed, allowing the system to distinguish binding overhead from execution time. -
Disable mechanism -- The
disable()method can prevent any further callback delivery, used when the request is cancelled or timed out.
50.2.13 The executeAppFunction Implementation Deep Dive¶
The system server's executeAppFunction method is the most critical path in
the entire framework. Let us trace it line by line from the AIDL entry point
through to the target service binding.
Step 1: Entry and initial validation.
// frameworks/base/services/appfunctions/.../AppFunctionManagerServiceImpl.java
@Override
public ICancellationSignal executeAppFunction(
@NonNull ExecuteAppFunctionAidlRequest requestInternal,
@NonNull IExecuteAppFunctionCallback executeAppFunctionCallback) {
int callingUid = Binder.getCallingUid();
int callingPid = Binder.getCallingPid();
final SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback =
initializeSafeExecuteAppFunctionCallback(
requestInternal, executeAppFunctionCallback, callingUid);
String validatedCallingPackage;
try {
validatedCallingPackage =
mCallerValidator.validateCallingPackage(requestInternal.getCallingPackage());
mCallerValidator.verifyTargetUserHandle(
requestInternal.getUserHandle(), validatedCallingPackage);
} catch (SecurityException exception) {
safeExecuteAppFunctionCallback.onError(
new AppFunctionException(
AppFunctionException.ERROR_DENIED, exception.getMessage()));
return null;
}
The SafeOneTimeExecuteAppFunctionCallback wrapper ensures that exactly one
response (success or error) is delivered, even if the target app sends multiple
replies or crashes before responding.
Step 2: Asynchronous execution on the thread pool.
ICancellationSignal localCancelTransport = CancellationSignal.createTransport();
THREAD_POOL_EXECUTOR.execute(
() -> {
try {
executeAppFunctionInternal(
requestInternal,
callingUid, callingPid,
localCancelTransport,
safeExecuteAppFunctionCallback,
executeAppFunctionCallback.asBinder());
} catch (Exception e) {
safeExecuteAppFunctionCallback.onError(
mapExceptionToExecuteAppFunctionResponse(e));
}
});
return localCancelTransport;
}
The work is dispatched to THREAD_POOL_EXECUTOR (defined in
AppFunctionExecutors) to avoid blocking the Binder thread pool.
Step 3: Permission and state validation.
@WorkerThread
private void executeAppFunctionInternal(...) {
// Enterprise policy check
if (!mCallerValidator.verifyEnterprisePolicyIsAllowed(callingUser, targetUser)) {
safeExecuteAppFunctionCallback.onError(
new AppFunctionException(
AppFunctionException.ERROR_ENTERPRISE_POLICY_DISALLOWED, ...));
return;
}
// Empty target package check
if (TextUtils.isEmpty(targetPackageName)) {
safeExecuteAppFunctionCallback.onError(
new AppFunctionException(
AppFunctionException.ERROR_INVALID_ARGUMENT, ...));
return;
}
Step 4: Future-chained permission and enabled-state checks.
The implementation uses AndroidFuture.thenCompose() for non-blocking
permission verification followed by AppSearch-backed enabled-state lookup:
mCallerValidator
.verifyCallerCanExecuteAppFunction(
callingUid, callingPid, targetUser,
requestInternal.getCallingPackage(),
targetPackageName,
requestInternal.getClientRequest().getFunctionIdentifier())
.thenCompose(canExecuteResult -> {
if (canExecuteResult == CAN_EXECUTE_APP_FUNCTIONS_DENIED) {
return AndroidFuture.failedFuture(
new SecurityException("Caller does not have permission"));
}
return isAppFunctionEnabled(
functionIdentifier, targetPackageName,
getAppSearchManagerAsUser(userHandle), THREAD_POOL_EXECUTOR)
.thenApply(isEnabled -> {
if (!isEnabled) {
throw new DisabledAppFunctionException("Disabled");
}
return canExecuteResult;
});
})
Step 5: Service resolution and binding.
.thenAccept(canExecuteResult -> {
int bindFlags = Context.BIND_AUTO_CREATE;
if (canExecuteResult
== CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_HAS_PERMISSION) {
bindFlags |= Context.BIND_FOREGROUND_SERVICE;
}
Intent serviceIntent =
mInternalServiceHelper.resolveAppFunctionService(
targetPackageName, targetUser);
// Grant implicit visibility to allow target to see caller
mPackageManagerInternal.grantImplicitAccess(
grantRecipientUserId, serviceIntent,
grantRecipientAppId, callingUid, /* direct= */ true);
bindAppFunctionServiceUnchecked(
requestInternal, serviceIntent, targetUser,
localCancelTransport, safeExecuteAppFunctionCallback,
bindFlags, callerBinder, callingUid);
})
This reveals an important detail: when the caller has
EXECUTE_APP_FUNCTIONS, the system uses BIND_FOREGROUND_SERVICE to elevate
the target service's process priority. Self-calls (same package) do not get
this elevation.
50.2.14 The RemoteServiceCaller Pattern¶
RemoteServiceCallerImpl implements the one-shot service binding pattern:
// frameworks/base/services/appfunctions/.../RemoteServiceCallerImpl.java
public class RemoteServiceCallerImpl<T> implements RemoteServiceCaller<T> {
public boolean runServiceCall(
Intent intent, int bindFlags, UserHandle userHandle,
long cancellationTimeoutMillis, CancellationSignal cancellationSignal,
RunServiceCallCallback<T> callback, IBinder callerBinder) {
OneOffServiceConnection serviceConnection =
new OneOffServiceConnection(intent, bindFlags, userHandle,
cancellationTimeoutMillis, cancellationSignal,
callback, callerBinder);
return serviceConnection.bindAndRun();
}
The OneOffServiceConnection is a ServiceConnection that:
- Calls
Context.bindServiceAsUser()to connect to the target - Sets a cancellation listener that triggers unbinding after a timeout
- Links to the caller's binder death to cancel if the caller dies
- Unbinds automatically after the callback completes
private class OneOffServiceConnection
implements ServiceConnection, ServiceUsageCompleteListener {
public boolean bindAndRun() {
boolean bindServiceResult =
mContext.bindServiceAsUser(mIntent, this, mFlags, mUserHandle);
if (bindServiceResult) {
mCancellationSignal.setOnCancelListener(() -> {
mCallback.onCancelled();
mHandler.postDelayed(mCancellationTimeoutRunnable,
mCancellationTimeoutMillis);
});
mDirectServiceVulture = () -> {
Slog.w(TAG, "Caller process onDeath signal received");
mCancellationSignal.cancel();
};
mCallerBinder.linkToDeath(mDirectServiceVulture, 0);
}
return bindServiceResult;
}
This pattern ensures that the service connection is always cleaned up, even if the caller crashes, the target crashes, or the user cancels.
50.2.15 Multi-User Support¶
The service implementation is multi-user aware. Each user has:
- Their own AppSearch database for function metadata
- Their own
PackageMonitorfor tracking package changes - Their own access history database
- Separate access flags per (agent, target) pair
// AppFunctionManagerServiceImpl.java
public void onUserUnlocked(TargetUser user) {
registerAppSearchObserver(user);
trySyncRuntimeMetadata(user);
PackageMonitor pkgMonitorForUser =
AppFunctionPackageMonitor.registerPackageMonitorForUser(mContext, user);
mPackageMonitors.append(user.getUserIdentifier(), pkgMonitorForUser);
if (accessCheckFlagsEnabled()) {
mMultiUserAppFunctionAccessHistory.onUserUnlocked(user);
}
}
public void onUserStopping(@NonNull TargetUser user) {
MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
mPackageMonitors.get(userIdentifier).unregister();
mPackageMonitors.delete(userIdentifier);
mMultiUserAppFunctionAccessHistory.onUserStopping(user);
}
50.2.16 Agent Allowlist Architecture¶
The agent allowlist has three tiers, merged at boot and on configuration changes:
graph TD
A["System Hardcoded<br/>(com.android.shell)"] --> D[Merged Allowlist]
B["DeviceConfig<br/>(machine_learning namespace)"] --> D
C["Settings.Secure<br/>(ADB override)"] --> D
D --> E{"Agent requesting<br/>execution?"}
E -->|In list| F[Allowed]
E -->|Not in list| G[ACCESS_REQUEST_STATE_UNREQUESTABLE]
// AppFunctionManagerServiceImpl.java
private static final List<SignedPackage> sSystemAllowlist =
List.of(new SignedPackage(SHELL_PKG, null));
@GuardedBy("mAgentAllowlistLock")
private List<SignedPackage> mUpdatableAgentAllowlist = Collections.emptyList();
@GuardedBy("mAgentAllowlistLock")
private List<SignedPackage> mSecureSettingAgentAllowlist = Collections.emptyList();
@GuardedBy("mAgentAllowlistLock")
private ArraySet<SignedPackage> mAgentAllowlist = new ArraySet<>(sSystemAllowlist);
The DeviceConfig.OnPropertiesChangedListener reloads the allowlist when
the server-side configuration changes:
private final DeviceConfig.OnPropertiesChangedListener mDeviceConfigListener =
properties -> {
if (properties.getKeyset().contains(ALLOWLISTED_APP_FUNCTIONS_AGENTS)) {
updateAgentAllowlist(true, false);
}
};
A ContentObserver watches for the ADB override:
private final ContentObserver mAdbAgentObserver =
new ContentObserver(FgThread.getHandler()) {
@Override
public void onChange(boolean selfChange, Uri uri) {
if (!ADDITIONAL_AGENTS_URI.equals(uri)) return;
updateAgentAllowlist(false, true);
}
};
50.2.17 URI Grants for AppFunction Responses¶
When a target app returns content URIs in its response, the framework can grant temporary URI permissions to the calling agent:
// AppFunctionManagerServiceImpl.java
private final IUriGrantsManager mUriGrantsManager;
private final UriGrantsManagerInternal mUriGrantsManagerInternal;
private final IBinder mPermissionOwner;
// In constructor:
mPermissionOwner = mUriGrantsManagerInternal.newUriPermissionOwner("appfunctions");
The AppFunctionUriGrant objects in the response specify which URIs should be
granted. These grants typically persist until device reboot.
50.2.18 Shell Command Support¶
The service implements onShellCommand() for developer debugging:
// AppFunctionManagerServiceImpl.java
@Override
public void onShellCommand(
FileDescriptor in, FileDescriptor out, FileDescriptor err,
@NonNull String[] args, ShellCallback callback,
@NonNull ResultReceiver resultReceiver) {
new AppFunctionManagerServiceShellCommand(mContext, this)
.exec(this, in, out, err, args, callback, resultReceiver);
}
Available via adb shell cmd app_function.
50.2.19 Boot Phase Handling¶
The service initializes its configuration during the
PHASE_SYSTEM_SERVICES_READY boot phase:
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
mBackgroundExecutor.execute(() ->
updateAgentAllowlist(true, true));
DeviceConfig.addOnPropertiesChangedListener(
NAMESPACE_MACHINE_LEARNING, mBackgroundExecutor, mDeviceConfigListener);
mContext.getContentResolver()
.registerContentObserver(ADDITIONAL_AGENTS_URI, false, mAdbAgentObserver);
}
}
50.3 Computer Control¶
Computer Control is Android 16's framework for allowing AI agents to programmatically interact with applications through a virtual display. Instead of requiring apps to implement specific APIs, an agent can launch any app on a headless virtual display, observe the screen via screenshots, inject tap/swipe events, and read accessibility trees -- the same paradigm used by "computer use" AI agents.
Source tree:
frameworks/base/core/java/android/companion/virtual/computercontrol/
ComputerControlSession.java (490 lines) -- Core session API
ComputerControlSessionParams.java (280 lines) -- Session configuration
InteractiveMirrorDisplay.java (72 lines) -- Mirror display for user view
AutomatedPackageListener.java -- Package change notifications
IComputerControlSession.aidl -- Session Binder interface
IComputerControlSessionCallback.aidl -- Creation lifecycle callback
IComputerControlStabilityListener.aidl -- UI stability signal
IInteractiveMirrorDisplay.aidl -- Mirror display interface
frameworks/base/libs/computercontrol/ -- Extension library
src/com/android/extensions/computercontrol/
ComputerControlExtensions.java (206 lines) -- Entry point
ComputerControlSession.java (684 lines) -- Extension session wrapper
InteractiveMirror.java -- Mirror abstraction
EventIdleTracker.java -- UI idle detection
StabilityHintCallbackTracker.java -- Stability signals
AutomatedPackageListener.java -- Extension listener
input/KeyEvent.java -- Input event wrapper
input/TouchEvent.java -- Touch event wrapper
view/MirrorView.java -- Mirror display view
50.3.1 Architecture¶
graph TB
subgraph "Agent App Process"
CCE[ComputerControlExtensions]
CCS_EXT["ComputerControlSession<br/>Extension"]
AP[AccessibilityDisplayProxy]
end
subgraph "system_server"
VDM[VirtualDeviceManager]
CCS_SVC["ComputerControlSession<br/>Service-side"]
VD[Virtual Display]
VKB[Virtual Keyboard]
VTS[Virtual Touchscreen]
end
subgraph "Target App"
ACTIVITY["Activity on<br/>Virtual Display"]
end
CCE -- "requestSession()" --> VDM
VDM -- "creates" --> VD
VDM -- "creates" --> VKB
VDM -- "creates" --> VTS
VDM -- "callback" --> CCS_EXT
CCS_EXT -- "tap/swipe/text" --> CCS_SVC
CCS_SVC -- "inject input" --> VTS
CCS_SVC -- "inject keys" --> VKB
VD -- "render" --> ACTIVITY
CCS_EXT -- "getScreenshot()" --> VD
AP -- "accessibility tree" --> ACTIVITY
50.3.2 Session Lifecycle¶
The entry point is ComputerControlExtensions.getInstance(), which checks for
FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS and VirtualDeviceManager
availability:
// frameworks/base/libs/computercontrol/.../ComputerControlExtensions.java
private static boolean isAvailable(Context context) {
if (!context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS)) {
return false;
}
return context.getSystemService(VirtualDeviceManager.class) != null;
}
Session creation flows through requestSession() which requires
ACCESS_COMPUTER_CONTROL:
// frameworks/base/libs/computercontrol/.../ComputerControlExtensions.java
@RequiresPermission(Manifest.permission.ACCESS_COMPUTER_CONTROL)
public void requestSession(@NonNull ComputerControlSession.Params params,
@NonNull Executor executor, @NonNull ComputerControlSession.Callback callback) {
// Build platform params
ComputerControlSessionParams sessionParams =
new ComputerControlSessionParams.Builder()
.setName(params.getName())
.setTargetPackageNames(params.getTargetPackageNames())
.setDisplayWidthPx(params.getDisplayWidthPx())
.setDisplayHeightPx(params.getDisplayHeightPx())
.setDisplayDpi(params.getDisplayDpi())
.setDisplaySurface(params.getDisplaySurface())
.setDisplayAlwaysUnlocked(params.isDisplayAlwaysUnlocked())
.build();
VirtualDeviceManager vdm = params.getContext().getSystemService(VirtualDeviceManager.class);
vdm.requestComputerControlSession(sessionParams, executor, sessionCallback);
}
The callback lifecycle mirrors VirtualDeviceManager session creation:
stateDiagram-v2
[*] --> Pending: requestSession
Pending --> UserApproval: onSessionPending intentSender
UserApproval --> Created: User approves
UserApproval --> Failed: User denies
Created --> Active: onSessionCreated session
Active --> Closed: close or framework event
Failed --> [*]: onSessionCreationFailed errorCode
Closed --> [*]: onSessionClosed
Error codes for session creation:
// frameworks/base/core/java/android/companion/virtual/computercontrol/ComputerControlSession.java
public static final int ERROR_SESSION_LIMIT_REACHED = 1;
public static final int ERROR_DEVICE_LOCKED = 2;
public static final int ERROR_PERMISSION_DENIED = 3;
50.3.3 The Core Session API¶
Once created, ComputerControlSession exposes a high-level input API:
// frameworks/base/core/java/android/companion/virtual/computercontrol/ComputerControlSession.java
// Launch an app
public void launchApplication(@NonNull String packageName);
// Hand over to user
public void handOverApplications();
// Screenshot
@Nullable public Image getScreenshot();
// Input injection
public void tap(int x, int y);
public void swipe(int fromX, int fromY, int toX, int toY);
public void longPress(int x, int y);
public void insertText(@NonNull String text, boolean replaceExisting, boolean commit);
public void performAction(@Action int actionCode);
// Low-level input
public void sendKeyEvent(@NonNull VirtualKeyEvent event);
public void sendTouchEvent(@NonNull VirtualTouchEvent event);
// Mirror display
@Nullable public InteractiveMirrorDisplay createInteractiveMirrorDisplay(
int width, int height, @NonNull Surface surface);
// UI stability
public void setStabilityListener(Executor executor, StabilityListener listener);
Screenshots are captured through an ImageReader that is attached to the
virtual display surface:
// frameworks/base/core/java/android/companion/virtual/computercontrol/ComputerControlSession.java
mImageReader = ImageReader.newInstance(displayInfo.logicalWidth,
displayInfo.logicalHeight, PixelFormat.RGBA_8888, /* maxImages= */ 2);
displayManagerGlobal.setVirtualDisplaySurface(displayToken, mImageReader.getSurface());
public Image getScreenshot() {
synchronized (mLock) {
return mImageReader == null ? null : mImageReader.acquireLatestImage();
}
}
50.3.4 Session Parameters¶
ComputerControlSessionParams configures the virtual display:
// frameworks/base/core/java/android/companion/virtual/computercontrol/ComputerControlSessionParams.java
public final class ComputerControlSessionParams implements Parcelable {
private final String mName;
private final List<String> mTargetPackageNames;
private final int mDisplayWidthPx;
private final int mDisplayHeightPx;
private final int mDisplayDpi;
private final Surface mDisplaySurface;
private final boolean mIsDisplayAlwaysUnlocked;
The targetPackageNames field restricts which apps can be launched in the
session. Each package must have a valid launcher intent and cannot be the
device permission controller.
50.3.5 Interactive Mirror Display¶
The InteractiveMirrorDisplay mirrors the session's virtual display and
allows a human user to observe and interact simultaneously:
// frameworks/base/core/java/android/companion/virtual/computercontrol/InteractiveMirrorDisplay.java
public final class InteractiveMirrorDisplay implements AutoCloseable {
public void resize(int width, int height);
public void sendTouchEvent(@NonNull VirtualTouchEvent event);
public void close();
}
This enables a "co-pilot" pattern where an AI agent drives the automation while a human watches and can intervene.
50.3.6 UI Stability Detection¶
Knowing when an app's UI has "settled" is critical for AI agents that need
to screenshot and analyze before acting. The StabilityListener interface
provides this signal:
// frameworks/base/core/java/android/companion/virtual/computercontrol/ComputerControlSession.java
public interface StabilityListener {
void onSessionStable();
}
The extension library's ComputerControlAccessibilityProxy and
EventIdleTracker monitor accessibility events and animation completion
to determine when the display content is stable.
50.3.7 Accessibility Integration¶
The extension-layer ComputerControlSession registers an
AccessibilityDisplayProxy for the virtual display, enabling the agent to
query the accessibility tree:
// frameworks/base/libs/computercontrol/.../ComputerControlSession.java
mAccessibilityProxy = new ComputerControlAccessibilityProxy(mVirtualDisplayId);
mAccessibilityManager.registerDisplayProxy(mAccessibilityProxy);
This gives the agent structured information about the UI (view hierarchy, content descriptions, bounding boxes) without relying solely on pixel-level screenshot analysis.
50.3.8 Automated Package Listener¶
Launcher apps can register to be notified when apps are being automated:
// frameworks/base/libs/computercontrol/.../ComputerControlExtensions.java
public void registerAutomatedPackageListener(
@NonNull Context context,
@NonNull @CallbackExecutor Executor executor,
@NonNull AutomatedPackageListener listener) {
VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
vdm.registerAutomatedPackageListener(executor, platformListener);
}
This allows the launcher to display an indicator that an app is currently under AI control.
50.3.9 Integration with VirtualDeviceManager¶
Computer Control builds on top of the VirtualDeviceManager framework (Chapter 21). The relationship is:
graph LR
CCE[ComputerControlExtensions] --> VDM[VirtualDeviceManager]
VDM --> VDD[VirtualDeviceParams]
VDM --> VDisplay[Virtual Display]
VDM --> VInput[Virtual Input Devices]
CCS[ComputerControlSession] --> VDisplay
CCS --> VInput
The key difference from general VirtualDevice usage is that Computer Control sessions create a trusted virtual display with input injection capabilities. The system server enforces that only the session owner can inject input events.
50.3.10 Extension-Layer Input Conversion¶
The extension library wraps platform input types with its own wrapper classes for API stability:
// frameworks/base/libs/computercontrol/.../ComputerControlSession.java
public void sendTouchEvent(TouchEvent touchEvent) {
VirtualTouchEvent virtualTouchEvent =
new VirtualTouchEvent.Builder()
.setX(touchEvent.getX())
.setY(touchEvent.getY())
.setPressure(touchEvent.getPressure())
.setToolType(touchEvent.getToolType())
.setAction(touchEvent.getAction())
.setPointerId(touchEvent.getPointerId())
.setEventTimeNanos(touchEvent.getEventTimeNanos())
.setMajorAxisSize(touchEvent.getMajorAxisSize())
.build();
mSession.sendTouchEvent(virtualTouchEvent);
mAccessibilityProxy.resetStabilityState();
if (mTouchListener != null) {
mTouchListener.onTouchEvent(touchEvent);
}
}
After every input injection, resetStabilityState() is called on the
accessibility proxy. This resets the stability timer, since the UI is now
expected to change.
50.3.11 Text Insertion API¶
For text fields, Computer Control provides a high-level insertText() method
that avoids the complexity of individual key events:
// frameworks/base/libs/computercontrol/.../ComputerControlSession.java
public void insertText(@NonNull String text, boolean replaceExisting, boolean commit) {
mSession.insertText(text, replaceExisting, commit);
mAccessibilityProxy.resetStabilityState();
}
This method uses InputConnection on the server side to directly manipulate
the text field's content, bypassing the virtual keyboard. The commit
parameter triggers an IME action (like pressing "Done" or "Send").
50.3.12 Touch Listener for Debugging¶
The extension session supports a TouchListener for observing all injected
touch events:
// frameworks/base/libs/computercontrol/.../ComputerControlSession.java
public interface TouchListener {
void onTouchEvent(@NonNull TouchEvent event);
}
public void setTouchListener(@Nullable TouchListener listener) {
mTouchListener = listener;
}
This is useful for logging, visualization, or coordinating multiple automation agents.
50.3.13 Interactive Mirror and Co-Pilot Pattern¶
The InteractiveMirror class in the extension layer wraps the platform's
InteractiveMirrorDisplay:
// frameworks/base/libs/computercontrol/.../ComputerControlSession.java
public InteractiveMirror createInteractiveMirror(
int width, int height, @NonNull Surface surface) {
InteractiveMirrorDisplay interactiveMirrorDisplay =
mSession.createInteractiveMirrorDisplay(width, height, surface);
if (interactiveMirrorDisplay == null) {
return null;
}
return new InteractiveMirror(interactiveMirrorDisplay);
}
This enables several important use cases:
- Debugging: Developers can watch AI automation in real-time
- Human-in-the-loop: A user can observe the AI's actions and intervene
- Streaming: The mirror can be used to broadcast automation sessions
- Multi-agent: One agent controls, another observes via the mirror
50.3.14 Session Close and Resource Cleanup¶
// frameworks/base/libs/computercontrol/.../ComputerControlSession.java
@Override
public void close() {
synchronized (mIsValid) {
if (!mIsValid.get()) {
return;
}
mAccessibilityManager.unregisterDisplayProxy(mAccessibilityProxy);
mSession.close();
mIsValid.set(false);
}
}
Close is idempotent (protected by AtomicBoolean mIsValid) and properly
unregisters the accessibility proxy before closing the platform session.
50.3.15 Stability Detection Architecture¶
The extension layer's stability detection combines multiple signals:
graph TB
A[Touch Event Injected] --> B[Reset Stability Timer]
C[Key Event Injected] --> B
D[App Launch] --> B
B --> E[Wait for Idle Period]
F[Accessibility Events] --> G[EventIdleTracker]
H[Window Transitions] --> G
I[Animations] --> G
G --> J{All signals idle?}
E --> J
J -->|Yes| K[onSessionStable]
J -->|No| L[Keep waiting]
The StabilityHintCallbackTracker in the extension layer handles the legacy
callback-based API, while the newer StabilityListener interface routes
through the platform-level IComputerControlStabilityListener AIDL interface.
50.3.16 Complete Extension Library File Inventory¶
| File | Lines | Purpose |
|---|---|---|
ComputerControlExtensions.java |
206 | Entry point, session factory |
ComputerControlSession.java |
684 | Extension session wrapper with accessibility |
InteractiveMirror.java |
86 | Mirror display wrapper |
EventIdleTracker.java |
92 | Accessibility event idle detection |
StabilityHintCallbackTracker.java |
55 | Legacy stability callback |
AutomatedPackageListener.java |
43 | Package automation notifications |
input/KeyEvent.java |
134 | Extension key event type |
input/TouchEvent.java |
296 | Extension touch event type |
view/MirrorView.java |
406 | Mirror display view widget |
50.3.17 Permission Model¶
Computer Control uses a layered permission model:
graph TD
A["ACCESS_COMPUTER_CONTROL<br/>(required to create session)"] --> B[Session Creation]
B --> C["User Approval<br/>(via IntentSender)"]
C --> D["Session Active"]
D --> E["Target Package Restriction<br/>(only named packages)"]
E --> F["Trusted Display<br/>(input injection allowed)"]
- The app must hold
ACCESS_COMPUTER_CONTROL - The system presents a user approval dialog via
IntentSender - Only packages listed in
targetPackageNamescan be launched - The virtual display is trusted, enabling input injection
- The permission controller package is always excluded from automation
50.4 OnDeviceIntelligence¶
The OnDeviceIntelligence (ODI) framework provides a system-level API for running large ML models (including LLMs) in a sandboxed process. It is designed around the principle that model weights and inference logic should never be directly accessible to the calling app.
Source tree:
frameworks/base/packages/NeuralNetworks/
framework/platform/java/android/app/ondeviceintelligence/
OnDeviceIntelligenceManager.java -- Client API
Feature.java -- Model feature descriptor
FeatureDetails.java -- Feature metadata
InferenceInfo.java -- Inference statistics
ProcessingCallback.java -- Non-streaming result callback
StreamingProcessingCallback.java -- Streaming result callback
OnDeviceIntelligenceException.java -- Typed errors
TokenInfo.java -- Token-level information
...
framework/platform/java/android/service/ondeviceintelligence/
OnDeviceSandboxedInferenceService.java -- Isolated inference service
OnDeviceIntelligenceService.java -- Non-isolated counterpart
...
service/platform/java/com/android/server/ondeviceintelligence/
OnDeviceIntelligenceManagerService.java -- SystemService
RemoteOnDeviceSandboxedInferenceService.java
RemoteOnDeviceIntelligenceService.java
ServiceConnector.java
InferenceInfoStore.java
...
50.4.1 Architecture¶
graph TB
subgraph "Calling App"
APP["App with<br/>USE_ON_DEVICE_INTELLIGENCE"]
ODIM[OnDeviceIntelligenceManager]
end
subgraph "system_server"
ODIMS[OnDeviceIntelligenceManagerService]
RODI[RemoteOnDeviceIntelligenceService]
RODSI[RemoteOnDeviceSandboxedInferenceService]
end
subgraph "OEM Intelligence Process"
ODIS[OnDeviceIntelligenceService]
STORAGE[Storage / Model Files]
end
subgraph "Isolated Process (android:isolatedProcess=true)"
ODSIS[OnDeviceSandboxedInferenceService]
MODEL[ML Model Runtime]
end
APP --> ODIM
ODIM -- "Binder" --> ODIMS
ODIMS --> RODI
ODIMS --> RODSI
RODI -- "bind" --> ODIS
RODSI -- "bind (isolated)" --> ODSIS
ODIS -- "file descriptors" --> ODSIS
ODSIS --> MODEL
50.4.2 The Client: OnDeviceIntelligenceManager¶
The manager is a @SystemApi service requiring USE_ON_DEVICE_INTELLIGENCE:
// frameworks/base/packages/NeuralNetworks/framework/platform/java/
// android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java
@SystemApi
@SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE)
public final class OnDeviceIntelligenceManager {
Key operations:
| Method | Purpose |
|---|---|
getVersion() |
Query remote implementation version |
getRemoteServicePackageName() |
Get the OEM package providing inference |
listFeatures() |
List available ML features/models |
getFeature() |
Get details of a specific feature |
requestFeatureDownload() |
Trigger model download |
processRequest() |
Non-streaming inference request |
processRequestStreaming() |
Streaming (token-by-token) inference |
getTokenInfo() |
Token counting/analysis |
registerLifecycleListener() |
Model load/unload notifications |
50.4.3 The Sandboxed Inference Service¶
The actual inference runs in an isolated process:
// frameworks/base/packages/NeuralNetworks/framework/platform/java/
// android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java
@SystemApi
public abstract class OnDeviceSandboxedInferenceService extends Service {
public static final String SERVICE_INTERFACE =
"android.service.ondeviceintelligence.OnDeviceSandboxedInferenceService";
The manifest declares:
<service android:name=".SampleSandboxedInferenceService"
android:permission="android.permission.BIND_ONDEVICE_SANDBOXED_INFERENCE_SERVICE"
android:isolatedProcess="true">
</service>
The isolatedProcess="true" flag means the service:
- Has no network access
- Has no access to the app's data directory
- Cannot access content providers
- Can only receive file descriptors passed explicitly by the system
Model weights reach the isolated process through ParcelFileDescriptor
objects passed by the OnDeviceIntelligenceService (the non-isolated
companion).
50.4.4 Dual-Service Architecture¶
ODI employs a two-service architecture:
graph LR
subgraph "Normal Process"
ODIS["OnDeviceIntelligenceService<br/>(has storage access)"]
end
subgraph "Isolated Process"
ODSIS["OnDeviceSandboxedInferenceService<br/>(no storage, no network)"]
end
ODIS -- "ParcelFileDescriptor<br/>(model weights)" --> ODSIS
ODIS -- "RemoteStorageService<br/>(read-only file access)" --> ODSIS
-
OnDeviceIntelligenceService -- runs in the OEM's normal process with full storage access. Handles model management, downloads, and serves model files to the isolated process.
-
OnDeviceSandboxedInferenceService -- runs in an isolated process. Performs actual inference. Receives model weights only through file descriptors. This design ensures that even a compromised inference engine cannot exfiltrate model weights or user data.
50.4.5 Model Lifecycle Events¶
The framework supports model load/unload broadcast notifications:
// OnDeviceSandboxedInferenceService.java
public static final String MODEL_LOADED_BROADCAST_INTENT =
"android.service.ondeviceintelligence.MODEL_LOADED";
public static final String MODEL_UNLOADED_BROADCAST_INTENT =
"android.service.ondeviceintelligence.MODEL_UNLOADED";
50.4.6 The System Service¶
OnDeviceIntelligenceManagerService extends SystemService and runs under
the SYSTEM user (not per-user), since ML models may have high memory
footprint:
// frameworks/base/packages/NeuralNetworks/service/platform/java/
// com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java
public class OnDeviceIntelligenceManagerService extends SystemService {
private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence";
private static final long MAX_AGE_MS = TimeUnit.HOURS.toMillis(3);
...
The service maintains connection state to both remote services and handles:
- Permission enforcement (only apps with
USE_ON_DEVICE_INTELLIGENCE) - Configuration via
DeviceConfignamespaceondeviceintelligence InferenceInfoStorefor tracking inference statistics- Temporary service overrides for testing
50.4.7 InferenceInfo¶
The framework introduces InferenceInfo for providing performance metadata:
// OnDeviceIntelligenceManager.java
public static final String KEY_REQUEST_INFERENCE_INFO = "request_inference_info";
When requested, the callback receives InferenceInfo containing timing and
throughput metrics from the inference run.
50.4.8 Feature Discovery and Download¶
The feature lifecycle follows a discover-download-use pattern:
sequenceDiagram
participant App
participant Manager as OnDeviceIntelligenceManager
participant Service as ManagerService
participant OEM as OnDeviceIntelligenceService
participant Sandbox as SandboxedInferenceService
App->>Manager: listFeatures(executor, callback)
Manager->>Service: IPC
Service->>OEM: listFeatures()
OEM-->>App: List<Feature>
App->>Manager: getFeatureDetails(feature, executor, callback)
Manager->>Service: IPC
Service->>OEM: getFeatureDetails()
OEM-->>App: FeatureDetails (status, size, etc.)
App->>Manager: requestFeatureDownload(feature, cancel, executor, callback)
Manager->>Service: IPC
Service->>OEM: requestFeatureDownload()
OEM-->>App: onDownloadStarted(bytesToDownload)
OEM-->>App: onDownloadProgress(bytesDownloaded)
OEM-->>App: onDownloadCompleted(downloadParams)
App->>Manager: processRequest(feature, request, cancel, executor, callback)
Manager->>Service: IPC
Service->>Sandbox: processRequest()
Sandbox-->>App: onResult(response)
The DownloadCallback interface provides fine-grained progress:
// OnDeviceIntelligenceManager.java
public void requestFeatureDownload(@NonNull Feature feature,
@Nullable CancellationSignal cancellationSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull DownloadCallback callback) {
Download failure reasons include:
DOWNLOAD_FAILURE_STATUS_DOWNLOADING-- Already downloadingDOWNLOAD_FAILURE_STATUS_NOT_ENOUGH_DISK_SPACEDOWNLOAD_FAILURE_STATUS_NETWORK_FAILURE
50.4.9 Processing Modes¶
ODI supports two processing modes:
Non-streaming (request/response):
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
public void processRequest(@NonNull Feature feature,
@NonNull @InferenceParams Bundle request,
@Nullable CancellationSignal cancellationSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull ProcessingCallback callback);
Streaming (token-by-token):
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
public void processRequestStreaming(@NonNull Feature feature,
@NonNull @InferenceParams Bundle request,
@Nullable CancellationSignal cancellationSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull StreamingProcessingCallback callback);
The streaming mode is essential for LLM inference, where generating a full response may take seconds but individual tokens arrive much faster.
50.4.10 Token Information¶
The requestTokenInfo() API computes token-level metadata without performing
full inference:
@RequiresPermission(Manifest.permission.USE_ON_DEVICE_INTELLIGENCE)
public void requestTokenInfo(@NonNull Feature feature,
@NonNull @InferenceParams Bundle request,
@Nullable CancellationSignal cancellationSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull OutcomeReceiver<TokenInfo, OnDeviceIntelligenceException> outcomeReceiver);
This is useful for:
- Counting tokens before inference (to check context limits)
- Estimating inference cost/time
- Token-level analysis without full generation
50.4.11 Lifecycle Listeners¶
Apps can register to be notified when models are loaded or unloaded:
// OnDeviceIntelligenceManager.java
private final Map<OnDeviceSandboxedInferenceService.LifecycleListener,
ILifecycleListener.Stub> mLifecycleListeners = new ConcurrentHashMap<>();
This allows apps to:
- Show loading indicators when a model is being loaded
- Adapt UI based on model availability
- Pre-warm by triggering model loading before the user needs it
50.4.12 Processing State Updates¶
The sandboxed service can update its processing state:
// OnDeviceSandboxedInferenceService.java
public static final String PROCESSING_STATE_BUNDLE_KEY = "processing_state";
State updates allow the system to track:
- Whether the service is actively processing
- How much memory the model is using
- Whether the service is in a degraded state
50.4.13 Configuration and DeviceConfig¶
The system service is controlled through the ondeviceintelligence
DeviceConfig namespace:
// OnDeviceIntelligenceManagerService.java
private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence";
private static final String KEY_SERVICE_ENABLED = "service_enabled";
private static final boolean DEFAULT_SERVICE_ENABLED = true;
OEMs configure the implementation package through system resources. The service can be temporarily overridden for testing via shell commands.
50.4.14 Streaming Inference Protocol Detail¶
The streaming API provides token-by-token delivery for LLM inference:
sequenceDiagram
participant App
participant Manager as OnDeviceIntelligenceManager
participant Service as ManagerService
participant Sandbox as SandboxedInferenceService
App->>Manager: processRequestStreaming(feature, request, callback)
Manager->>Service: IPC (IStreamingResponseCallback)
Service->>Sandbox: processRequestStreaming()
loop For each generated token
Sandbox->>Service: onNewContent(partialResult)
Service->>Manager: IStreamingResponseCallback.onNewContent()
Manager->>App: StreamingProcessingCallback.onPartialResult(bundle)
end
Sandbox->>Service: onSuccess(finalResult)
Service->>Manager: IStreamingResponseCallback.onSuccess()
Manager->>App: StreamingProcessingCallback.onResult(bundle)
The IStreamingResponseCallback defines the wire protocol:
// OnDeviceIntelligenceManager.java (processRequestStreaming)
IStreamingResponseCallback callback = new IStreamingResponseCallback.Stub() {
@Override
public void onNewContent(@InferenceParams Bundle result) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
() -> streamingProcessingCallback.onPartialResult(result));
});
}
@Override
public void onSuccess(@InferenceParams Bundle result) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
() -> streamingProcessingCallback.onResult(result));
});
}
@Override
public void onFailure(int errorCode, String errorMessage,
PersistableBundle errorParams) {
Binder.withCleanCallingIdentity(() -> {
callbackExecutor.execute(
() -> streamingProcessingCallback.onError(
new OnDeviceIntelligenceException(
errorCode, errorMessage, errorParams)));
});
}
50.4.15 Data Augmentation Protocol¶
A unique feature of ODI is the data augmentation callback, which allows the sandboxed inference service to request additional data from the calling app mid-inference:
// OnDeviceIntelligenceManager.java
@Override
public void onDataAugmentRequest(@NonNull @InferenceParams Bundle request,
@NonNull RemoteCallback contentCallback) {
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
() -> processingCallback.onDataAugmentRequest(request, result -> {
Bundle bundle = new Bundle();
bundle.putParcelable(AUGMENT_REQUEST_CONTENT_BUNDLE_KEY, result);
callbackExecutor.execute(() -> contentCallback.sendResult(bundle));
})));
}
sequenceDiagram
participant App
participant Sandbox as SandboxedInferenceService
App->>Sandbox: processRequest(initialData)
Sandbox->>Sandbox: Begin inference
Note over Sandbox: Needs additional context
Sandbox->>App: onDataAugmentRequest(request)
App->>App: Fetch additional data
App->>Sandbox: contentCallback.sendResult(augmentedData)
Sandbox->>Sandbox: Continue inference with augmented data
Sandbox->>App: onResult(finalResponse)
This pattern enables retrieval-augmented generation (RAG) where the model can request relevant documents mid-generation.
50.4.16 ProcessingSignal¶
Beyond CancellationSignal, ODI provides a ProcessingSignal for
sending custom control signals to the inference service during processing:
// OnDeviceIntelligenceManager.java
public void processRequest(@NonNull Feature feature,
@NonNull @InferenceParams Bundle request,
@RequestType int requestType,
@Nullable CancellationSignal cancellationSignal,
@Nullable ProcessingSignal processingSignal,
@NonNull @CallbackExecutor Executor callbackExecutor,
@NonNull ProcessingCallback processingCallback) {
This allows apps to:
- Adjust generation parameters mid-stream (e.g., change temperature)
- Signal context updates
- Provide real-time feedback to the model
50.4.17 Power Attribution¶
ODI tracks inference power usage for attribution:
// OnDeviceIntelligenceManager.java
@RequiresPermission(Manifest.permission.DUMP)
@FlaggedApi(FLAG_ON_DEVICE_INTELLIGENCE_25Q4)
public @NonNull List<InferenceInfo> getLatestInferenceInfo(
@CurrentTimeMillisLong long startTimeEpochMillis) {
return mService.getLatestInferenceInfo(startTimeEpochMillis);
}
This allows the system to correctly attribute battery usage to the app that triggered the inference rather than blaming the inference service itself.
50.4.18 Security Boundaries¶
The ODI framework enforces several security boundaries:
graph TB
subgraph "App Process"
A["App<br/>(USE_ON_DEVICE_INTELLIGENCE)"]
end
subgraph "system_server"
B["ManagerService<br/>(permission enforcement)"]
end
subgraph "OEM Process"
C["IntelligenceService<br/>(model management,<br/>storage access)"]
end
subgraph "Isolated Process"
D["SandboxedInferenceService<br/>(NO network, NO storage,<br/>NO content providers)"]
end
A -->|"permission gate"| B
B -->|"bind normal"| C
B -->|"bind isolated"| D
C -->|"ParcelFileDescriptor only"| D
style D fill:#ffe0e0
Key restrictions on the isolated process:
- No network access (android:isolatedProcess=true)
- No access to app data directory
- No access to content providers
- Can only receive explicitly passed file descriptors
- Memory limits enforced by the system
- Process can be killed by the system at any time
This design means that even if an attacker compromises the inference engine (e.g., through a model weight poisoning attack), they cannot exfiltrate data from the device.
50.5 NeuralNetworks (NNAPI)¶
The Neural Networks API (NNAPI) is AOSP's hardware abstraction for accelerated ML inference. It has been part of AOSP since Android 8.1 and is now delivered as a Mainline module.
Source tree:
packages/modules/NeuralNetworks/ (104 MB)
runtime/ -- C++ runtime library
NeuralNetworks.cpp -- C API entry points
Manager.cpp (1376 lines) -- Device management
CompilationBuilder.cpp -- Model compilation
ExecutionBuilder.cpp -- Inference execution
ExecutionPlan.cpp -- Multi-device partitioning
...
common/types/include/nnapi/
IDevice.h -- HAL device interface
Types.h -- Shared type definitions
driver/ -- Reference CPU driver
framework/ -- Java/AIDL framework
service/ -- NNAPI service
extensions/ -- Vendor extensions
shim_and_sl/ -- Support library / shim
50.5.1 Architecture¶
graph TB
subgraph "App Process"
APP["ML Framework<br/>(TFLite, ONNX, etc.)"]
CAPI["C API<br/>(NeuralNetworks.h)"]
end
subgraph "NNAPI Runtime"
MGR["Manager<br/>(device discovery)"]
COMP["CompilationBuilder<br/>(model optimization)"]
EXEC["ExecutionBuilder<br/>(inference dispatch)"]
PLAN["ExecutionPlan<br/>(multi-device partitioning)"]
BURST["BurstBuilder<br/>(reusable execution)"]
end
subgraph "HAL Layer"
IDEV["IDevice<br/>(driver interface)"]
IPM["IPreparedModel<br/>(compiled model)"]
IBUF["IBuffer<br/>(shared memory)"]
end
subgraph "Hardware"
CPU["CPU<br/>(reference)"]
GPU["GPU"]
DSP["DSP"]
NPU["NPU / TPU"]
end
APP --> CAPI
CAPI --> MGR
MGR --> COMP
COMP --> EXEC
EXEC --> PLAN
PLAN --> BURST
BURST --> IDEV
IDEV --> IPM
IPM --> IBUF
IDEV --> CPU
IDEV --> GPU
IDEV --> DSP
IDEV --> NPU
50.5.2 The C API¶
The public API is a C interface defined in NeuralNetworks.h. The
implementation in NeuralNetworks.cpp validates parameters and delegates to
C++ builder classes:
// packages/modules/NeuralNetworks/runtime/NeuralNetworks.cpp
// Contains all the entry points to the C Neural Networks API.
// We do basic validation of the operands and then call the class
// that implements the functionality.
Key data types verified at compile time:
static_assert(ANEURALNETWORKS_FLOAT32 == 0, "...");
static_assert(ANEURALNETWORKS_INT32 == 1, "...");
static_assert(ANEURALNETWORKS_UINT32 == 2, "...");
static_assert(ANEURALNETWORKS_TENSOR_FLOAT32 == 3, "...");
static_assert(ANEURALNETWORKS_TENSOR_INT32 == 4, "...");
static_assert(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM == 5, "...");
50.5.3 The Runtime Pipeline¶
The NNAPI execution pipeline has four stages:
graph LR
A["1. Model<br/>Definition"] --> B["2. Compilation"]
B --> C["3. Execution"]
C --> D["4. Result<br/>Retrieval"]
A2["ANeuralNetworksModel_create()"] --> A
B2["ANeuralNetworksCompilation_create()"] --> B
C2["ANeuralNetworksExecution_create()"] --> C
D2["ANeuralNetworksExecution_getOutput*()"] --> D
-
Model Definition -- Build a computation graph with operands and operations. Each operation maps to a standardized neural network operator (convolution, pooling, activation, etc.).
-
Compilation -- The
CompilationBuilderselects devices, partitions the model across multiple accelerators if beneficial, and generates device-specific code. -
Execution -- The
ExecutionBuilderdispatches work to devices. Can be synchronous, asynchronous, or fenced. -
Result Retrieval -- Output tensors are read from shared memory buffers.
50.5.4 The HAL: IDevice¶
The IDevice interface represents a hardware accelerator driver:
// packages/modules/NeuralNetworks/common/types/include/nnapi/IDevice.h
class IDevice {
public:
virtual const std::string& getName() const = 0;
virtual const std::string& getVersionString() const = 0;
virtual Version getFeatureLevel() const = 0;
virtual DeviceType getType() const = 0;
// Model compilation
virtual GeneralResult<SharedPreparedModel> prepareModel(...) const = 0;
// Memory allocation
virtual GeneralResult<SharedBuffer> allocate(...) const = 0;
...
Device types include:
| Type | Description |
|---|---|
DeviceType::CPU |
Reference CPU implementation |
DeviceType::GPU |
Graphics processing unit |
DeviceType::ACCELERATOR |
Dedicated ML accelerator (NPU/TPU) |
DeviceType::OTHER |
Other hardware |
50.5.5 Multi-Device Partitioning¶
The ExecutionPlan handles model partitioning across multiple devices.
If a model contains operations that different accelerators handle best,
NNAPI can split the model:
graph TB
subgraph "Model Graph"
OP1[Conv2D] --> OP2[ReLU]
OP2 --> OP3[MaxPool]
OP3 --> OP4[FullyConnected]
OP4 --> OP5[Softmax]
end
subgraph "Partitioned"
P1["Partition 1: GPU<br/>Conv2D + ReLU + MaxPool"]
P2["Partition 2: NPU<br/>FullyConnected + Softmax"]
end
OP3 --> P1
OP5 --> P2
P1 -- "shared memory" --> P2
50.5.6 Burst Execution¶
The BurstBuilder creates a reusable execution context for repeated
inferences with different input data but the same model. This amortizes
compilation and setup costs:
// packages/modules/NeuralNetworks/runtime/Manager.h
class RuntimeExecution {
public:
virtual std::tuple<int, std::vector<OutputShape>, Timing> compute(
const SharedBurst& burstController,
const OptionalTimePoint& deadline) const = 0;
virtual std::tuple<int, int, ExecuteFencedInfoCallback, Timing> computeFenced(
const std::vector<int>& waitFor,
const OptionalTimePoint& deadline,
const OptionalDuration& timeoutDurationAfterFence) const = 0;
};
50.5.7 Vendor Extensions¶
The extensions/ directory allows vendors to define custom operations and
data types beyond the standard NNAPI specification. Extensions use a
namespaced identifier to avoid conflicts:
50.5.8 Support Library and Shim¶
The shim_and_sl/ directory provides:
-
Support Library (SL): A standalone library that apps can bundle for consistent NNAPI behavior across Android versions.
-
Shim: Bridges between AIDL and HIDL HAL versions for backward compatibility.
50.5.9 The RuntimePreparedModel Abstraction¶
The RuntimePreparedModel provides a unified interface for both hardware
accelerated and CPU-based execution:
// packages/modules/NeuralNetworks/runtime/Manager.h
class RuntimePreparedModel {
public:
virtual const Device* getDevice() const = 0;
virtual SharedPreparedModel getInterface() const = 0;
virtual std::tuple<int, std::vector<OutputShape>, Timing> execute(
const std::vector<ModelArgumentInfo>& inputs,
const std::vector<ModelArgumentInfo>& outputs,
const std::vector<const RuntimeMemory*>& memories,
const SharedBurst& burstController,
MeasureTiming measure,
const OptionalTimePoint& deadline,
const OptionalDuration& loopTimeoutDuration,
const std::vector<TokenValuePair>& metaData) const = 0;
virtual std::tuple<int, int, ExecuteFencedInfoCallback, Timing> executeFenced(
const std::vector<ModelArgumentInfo>& inputs,
const std::vector<ModelArgumentInfo>& outputs,
const std::vector<const RuntimeMemory*>& memories,
const std::vector<int>& waitFor,
MeasureTiming measure,
const OptionalTimePoint& deadline,
const OptionalDuration& loopTimeoutDuration,
const OptionalDuration& timeoutDurationAfterFence,
const std::vector<TokenValuePair>& metaData) const = 0;
The executeFenced variant supports:
- Wait-for fences: Synchronize with other GPU/DSP work
- Timeout after fence: Set a deadline relative to fence signaling
- Timing measurement: Optionally collect execution timing
50.5.10 NNAPI Data Types¶
The C API defines a rich set of tensor and scalar types:
// packages/modules/NeuralNetworks/runtime/NeuralNetworks.cpp
static_assert(ANEURALNETWORKS_FLOAT32 == 0);
static_assert(ANEURALNETWORKS_INT32 == 1);
static_assert(ANEURALNETWORKS_UINT32 == 2);
static_assert(ANEURALNETWORKS_TENSOR_FLOAT32 == 3);
static_assert(ANEURALNETWORKS_TENSOR_INT32 == 4);
static_assert(ANEURALNETWORKS_TENSOR_QUANT8_ASYMM == 5);
static_assert(ANEURALNETWORKS_BOOL == 6);
static_assert(ANEURALNETWORKS_TENSOR_QUANT16_SYMM == 7);
static_assert(ANEURALNETWORKS_TENSOR_FLOAT16 == 8);
static_assert(ANEURALNETWORKS_TENSOR_BOOL8 == 9);
static_assert(ANEURALNETWORKS_FLOAT16 == 10);
static_assert(ANEURALNETWORKS_TENSOR_QUANT8_SYMM_PER_CHANNEL == 11);
static_assert(ANEURALNETWORKS_TENSOR_QUANT16_ASYMM == 12);
static_assert(ANEURALNETWORKS_TENSOR_QUANT8_SYMM == 13);
The static_assert checks guarantee ABI stability -- if any constant changes,
compilation fails.
50.5.11 Device Discovery¶
The Manager class discovers available accelerators at runtime:
graph TB
MGR["Manager::getDevices()"]
MGR --> REG["Device Registry"]
REG --> HAL_DEV1["HAL Device 1<br/>(GPU via AIDL)"]
REG --> HAL_DEV2["HAL Device 2<br/>(NPU via AIDL)"]
REG --> HAL_DEV3["HAL Device 3<br/>(DSP via HIDL shim)"]
REG --> CPU_DEV["CPU Reference<br/>(built-in)"]
The Manager:
- Queries the
IDeviceservice manager for registered accelerators - Reads their capabilities (supported operations, performance info)
- Maintains a device list for model compilation and execution
- Falls back to the CPU reference implementation if no accelerators match
50.5.12 Memory Management¶
NNAPI uses shared memory for zero-copy data transfer between the app and accelerators:
graph LR
APP["App Memory<br/>(AHardwareBuffer)"] --> SHARED["Shared Memory<br/>(ashmem / ion)"]
SHARED --> ACCEL["Accelerator<br/>DMA"]
The RuntimeMemory class manages memory pools:
- AHardwareBuffer: For GPU-accessible memory
- Ashmem: For CPU-to-accelerator sharing
- Ion/DMA-buf: For direct hardware DMA access
50.5.13 NNAPI Feature Levels¶
NNAPI has evolved through several feature levels, each adding new operations and capabilities:
| Feature Level | Android Version | Key Additions |
|---|---|---|
| 1 | 8.1 (API 27) | Basic ops: Conv2D, MaxPool, ReLU |
| 2 | 9 (API 28) | BatchNorm, LSTM, more quantized ops |
| 3 | 10 (API 29) | Control flow (IF, WHILE), fenced execution |
| 4 | 11 (API 30) | Quality of service, model priority |
| 5 | 12 (API 31) | Signed 8-bit quantization |
| 6 | 13 (API 33) | AIDL HAL interface |
| 7 | 14 (API 34) | Vendor extensions |
| 8 | 15 (API 35) | Flatbuffer model format |
50.5.14 Telemetry¶
The runtime includes a Telemetry module that collects anonymized performance
metrics:
Metrics include:
- Compilation time per device
- Execution latency
- Error rates
- Device selection outcomes
- Memory allocation patterns
50.5.15 The NNAPI C API Lifecycle¶
A complete NNAPI workflow involves these API calls in order:
graph TD
A["ANeuralNetworksModel_create()"] --> B["ANeuralNetworksModel_addOperand()<br/>(repeat for each tensor)"]
B --> C["ANeuralNetworksModel_setOperandValue()<br/>(for constants)"]
C --> D["ANeuralNetworksModel_addOperation()<br/>(repeat for each op)"]
D --> E["ANeuralNetworksModel_identifyInputsAndOutputs()"]
E --> F["ANeuralNetworksModel_finish()"]
F --> G["ANeuralNetworksCompilation_create()"]
G --> H["ANeuralNetworksCompilation_setPreference()"]
H --> I["ANeuralNetworksCompilation_finish()"]
I --> J["ANeuralNetworksExecution_create()"]
J --> K["ANeuralNetworksExecution_setInput()<br/>(bind input buffers)"]
K --> L["ANeuralNetworksExecution_setOutput()<br/>(bind output buffers)"]
L --> M["ANeuralNetworksExecution_compute()<br/>or startCompute()"]
M --> N["Read output buffers"]
N --> O["ANeuralNetworksExecution_free()"]
O --> P["ANeuralNetworksCompilation_free()"]
P --> Q["ANeuralNetworksModel_free()"]
50.5.16 Compilation Preferences¶
// ANeuralNetworksCompilation_setPreference() options:
ANEURALNETWORKS_PREFER_LOW_POWER // Battery efficient
ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER // Minimum latency
ANEURALNETWORKS_PREFER_SUSTAINED_SPEED // Sustained throughput
These preferences guide device selection:
LOW_POWERmay prefer a DSP over a GPUFAST_SINGLE_ANSWERmay prefer GPU with highest peak performanceSUSTAINED_SPEEDmay prefer a device with thermal headroom
50.5.17 Error Handling¶
NNAPI uses integer error codes for all operations:
| Code | Name | Meaning |
|---|---|---|
| 0 | ANEURALNETWORKS_NO_ERROR |
Success |
| 1 | ANEURALNETWORKS_OUT_OF_MEMORY |
Memory allocation failed |
| 2 | ANEURALNETWORKS_INCOMPLETE |
Operation not yet completed |
| 3 | ANEURALNETWORKS_UNEXPECTED_NULL |
Null pointer where non-null expected |
| 4 | ANEURALNETWORKS_BAD_DATA |
Invalid model or data |
| 5 | ANEURALNETWORKS_OP_FAILED |
Hardware execution failure |
| 6 | ANEURALNETWORKS_BAD_STATE |
Invalid state for this operation |
| 7 | ANEURALNETWORKS_UNMAPPABLE |
Cannot map to this device |
| 8 | ANEURALNETWORKS_OUTPUT_INSUFFICIENT_SIZE |
Output buffer too small |
| 9 | ANEURALNETWORKS_UNAVAILABLE_DEVICE |
Device unavailable |
| 10 | ANEURALNETWORKS_MISSED_DEADLINE_TRANSIENT |
Temporary deadline miss |
| 11 | ANEURALNETWORKS_MISSED_DEADLINE_PERSISTENT |
Persistent deadline miss |
| 12 | ANEURALNETWORKS_RESOURCE_EXHAUSTED_TRANSIENT |
Temporary resource exhaustion |
| 13 | ANEURALNETWORKS_RESOURCE_EXHAUSTED_PERSISTENT |
Persistent resource exhaustion |
| 14 | ANEURALNETWORKS_DEAD_OBJECT |
Driver process died |
50.5.18 Supported Operations¶
NNAPI supports over 100 neural network operations including:
Activation functions:
- RELU, RELU1, RELU6
- LOGISTIC (sigmoid)
- TANH
- ELU, HARD_SWISH
Convolution:
- CONV_2D, DEPTHWISE_CONV_2D
- TRANSPOSE_CONV_2D
- GROUPED_CONV_2D
Pooling:
- AVERAGE_POOL_2D, MAX_POOL_2D
- L2_POOL_2D
Normalization:
- BATCH_NORMALIZATION
- L2_NORMALIZATION
- LOCAL_RESPONSE_NORMALIZATION
- INSTANCE_NORMALIZATION
Recurrent:
- LSTM, UNIDIRECTIONAL_SEQUENCE_LSTM
- BIDIRECTIONAL_SEQUENCE_LSTM
- UNIDIRECTIONAL_SEQUENCE_RNN
- BIDIRECTIONAL_SEQUENCE_RNN
Element-wise:
- ADD, SUB, MUL, DIV
- FLOOR, CEIL, ABS, NEG
- POW, SQRT, RSQRT, EXP, LOG
- SIN, MINIMUM, MAXIMUM
- LESS, LESS_EQUAL, EQUAL, NOT_EQUAL
Shape manipulation:
- RESHAPE, SQUEEZE, EXPAND_DIMS
- CONCATENATION, SPLIT
- TRANSPOSE, GATHER, SELECT
- SLICE, STRIDED_SLICE, PAD
- TILE, REVERSE, BATCH_TO_SPACE_ND
Control flow:
- IF, WHILE (added in Feature Level 3)
50.5.19 Module Delivery and Updates¶
NNAPI is delivered as part of the NeuralNetworks Mainline module
(com.android.neuralnetworks), which allows:
- Security patches without full OS update
- New operation support for existing devices
- Bug fixes independent of OEM update cycles
- Consistent behavior across devices
The module is built from:
50.6 OnDevicePersonalization and Federated Learning¶
The OnDevicePersonalization (ODP) Mainline module provides infrastructure for privacy-preserving machine learning that keeps raw data on-device while producing useful aggregate models.
Source tree:
packages/modules/OnDevicePersonalization/ (642 files)
framework/ -- Public API
federatedcompute/ -- Federated learning engine
src/com/android/federatedcompute/services/
training/
IsolatedTrainingService.java -- Isolated TFLite runtime
IsolatedTrainingServiceImpl.java
examplestore/ -- Training data management
scheduling/ -- Job scheduling
common/ -- Shared utilities
systemservice/ -- System service
pluginlib/ -- Plugin interface for OEMs
samples/ -- Sample implementations
50.6.1 Architecture¶
graph TB
subgraph "App Process"
APP_DATA[App Data]
ODP_CLIENT[ODP Client API]
end
subgraph "ODP Module Process"
ODP_SVC["OnDevicePersonalization<br/>Service"]
FC_SCHED["Federated Compute<br/>Scheduler"]
EXAMPLE_STORE[Example Store]
end
subgraph "Isolated Process"
ITS[IsolatedTrainingService]
TFLITE[TFLite Runtime]
end
subgraph "Remote Server"
FC_SERVER["Federated Compute<br/>Server"]
end
APP_DATA --> ODP_CLIENT
ODP_CLIENT --> ODP_SVC
ODP_SVC --> FC_SCHED
FC_SCHED --> EXAMPLE_STORE
EXAMPLE_STORE --> ITS
ITS --> TFLITE
FC_SCHED -- "aggregated updates<br/>(differential privacy)" --> FC_SERVER
FC_SERVER -- "global model<br/>updates" --> FC_SCHED
50.6.2 Federated Learning Concepts¶
Federated learning trains a shared model across many devices without centralizing the training data:
sequenceDiagram
participant Server as Federated Compute Server
participant Device1 as Device A
participant Device2 as Device B
participant Device3 as Device C
Server->>Device1: Send global model v1
Server->>Device2: Send global model v1
Server->>Device3: Send global model v1
Device1->>Device1: Train on local data
Device2->>Device2: Train on local data
Device3->>Device3: Train on local data
Device1->>Server: Send gradient update (+ noise)
Device2->>Server: Send gradient update (+ noise)
Device3->>Server: Send gradient update (+ noise)
Server->>Server: Aggregate updates, Apply differential privacy
Server->>Device1: Send global model v2
50.6.3 IsolatedTrainingService¶
The actual TFLite training runs in an isolated process:
// packages/modules/OnDevicePersonalization/federatedcompute/
// src/.../training/IsolatedTrainingService.java
public class IsolatedTrainingService extends Service {
private IIsolatedTrainingService.Stub mBinder;
@Override
public void onCreate() {
mBinder = new IsolatedTrainingServiceImpl(this);
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
The IsolatedTrainingServiceImpl loads the TFLite runtime and executes
training rounds. Training data is provided through an ExampleStore
abstraction that iterates over the device's local examples without exposing
raw data to the network-connected scheduling process.
50.6.4 Example Store¶
The example store provides training data to the isolated process:
federatedcompute/src/.../examplestore/
ExampleIterator.java -- Iterator interface
FederatedExampleIterator.java -- Federated compute iterator
ExampleConsumptionRecorder.java -- Tracks data usage
ExampleStoreServiceProvider.java -- Service binding
50.6.5 Scheduling and Conditions¶
Federated compute jobs are scheduled through Android's JobScheduler
with conditions that protect user experience:
Training runs only when the device is:
- Charging (or above a battery threshold)
- Connected to unmetered network (Wi-Fi)
- Idle
These conditions are tracked by BatteryInfo and NetworkStats in the
common/ package.
50.6.6 Privacy Protections¶
The federated compute protocol applies multiple privacy layers:
- Isolated process: Training data never leaves the isolated process
- Secure aggregation: Individual updates are encrypted before sending
- Differential privacy: Noise is added to gradient updates
- Minimum cohort size: Updates are only accepted from groups above a threshold, preventing single-device fingerprinting
50.6.7 Federated Compute Module Structure¶
packages/modules/OnDevicePersonalization/federatedcompute/
src/com/android/federatedcompute/services/
training/
IsolatedTrainingService.java -- Isolated service entry
IsolatedTrainingServiceImpl.java -- Training logic
examplestore/
ExampleIterator.java -- Training data iterator
FederatedExampleIterator.java -- Federated-specific iterator
ExampleConsumptionRecorder.java -- Usage tracking
ExampleStoreServiceProvider.java -- Service binding
scheduling/
FederatedComputeJobManager.java -- Job scheduling
common/
Flags.java -- Feature flags
PhFlags.java -- Phone-home flags
Constants.java -- Shared constants
FederatedComputeExecutors.java -- Thread pools
BatteryInfo.java -- Battery state
NetworkStats.java -- Network conditions
TrainingEventLogger.java -- Metrics
TrainingResult.java -- Training outcome
50.6.8 Training Protocol¶
The federated training protocol follows these steps on each device:
graph TB
A["Scheduler triggers<br/>training job"] --> B{Check conditions}
B -->|Charging + WiFi + Idle| C["Download global model<br/>from server"]
B -->|Conditions not met| Z[Skip this round]
C --> D["Load model in<br/>IsolatedTrainingService"]
D --> E["Iterate over<br/>local examples"]
E --> F["Compute local<br/>gradient update"]
F --> G["Clip gradient<br/>to bounded norm"]
G --> H["Add calibrated<br/>noise"]
H --> I["Encrypt with<br/>secure aggregation key"]
I --> J["Upload encrypted<br/>update to server"]
J --> K["Server aggregates<br/>once cohort is complete"]
K --> L["New global model<br/>available"]
50.6.9 Example Store Architecture¶
The example store provides a clean abstraction for training data:
graph TB
subgraph "App Process"
APP_DATA[App-Specific Data]
end
subgraph "ODP Module"
ESP[ExampleStoreServiceProvider]
EI[ExampleIterator]
end
subgraph "Isolated Training Process"
FEI[FederatedExampleIterator]
TF[TFLite Training]
end
APP_DATA --> ESP
ESP --> EI
EI --> FEI
FEI --> TF
The ExampleConsumptionRecorder tracks which training examples have been
used, preventing over-representation of frequently available data.
50.6.10 Plugin Architecture¶
OEMs can extend ODP through the plugin library:
Plugins allow OEMs to:
- Provide custom example stores
- Implement device-specific training optimizations
- Add custom metrics collection
- Define custom scheduling policies
50.7 Content Capture and Intelligence¶
Three framework services work together to capture UI state, classify text entities, and predict app usage. These services form the "passive intelligence" layer that powers features like Smart Linkify, Smart Copy, and app usage predictions.
50.7.1 ContentCaptureManager¶
The Content Capture subsystem silently captures the structure and content of activities as the user interacts with them:
// frameworks/base/core/java/android/view/contentcapture/ContentCaptureManager.java
@SystemService(Context.CONTENT_CAPTURE_MANAGER_SERVICE)
public final class ContentCaptureManager {
Source:
frameworks/base/core/java/android/view/contentcapture/ContentCaptureManager.java
(1221 lines)
From the Javadoc:
Content capture provides real-time, continuous capture of application activity, display and events to an intelligence service that is provided by the Android system. The intelligence service then uses that info to mediate and speed user journey through different apps.
Design principles:
| Concern | Mechanism |
|---|---|
| Privacy | Intelligence service is a trusted system component; cannot be changed by user; data used only for on-device ML; enforced by process isolation and CDD |
| Performance | Only enabled for allowlisted apps/activities; events are buffered and sent in batches |
50.7.2 ContentCaptureService¶
The service side receives captured content:
frameworks/base/core/java/android/service/contentcapture/
ContentCaptureService.java -- Abstract service base
ContentCaptureServiceInfo.java -- Service metadata
IContentCaptureService.aidl -- Binder interface
ActivityEvent.java -- Activity lifecycle events
FlushMetrics.java -- Batching metrics
DataShareCallback.java -- Data export
sequenceDiagram
participant Activity
participant CCSession as ContentCaptureSession
participant CCM as ContentCaptureManager
participant CCMS as ContentCaptureManagerService
participant CCS as ContentCaptureService (OEM implementation)
Activity->>CCSession: onStart/onResume
CCSession->>CCSession: Capture view structure
CCSession->>CCM: Buffer events
CCM->>CCMS: Flush batch
CCMS->>CCS: onContentCaptureEvents(sessionId, events)
CCS->>CCS: ML analysis (entity detection, context building)
50.7.3 TextClassifierService¶
The TextClassifierService provides entity classification for text:
Capabilities:
| API | Function |
|---|---|
onSuggestSelection() |
Expand a text selection to cover a complete entity |
onClassifyText() |
Classify selected text (phone, email, address, etc.) |
onGenerateLinks() |
Generate clickable links for entities in text |
onDetectLanguage() |
Detect the language of a text span |
onSuggestConversationActions() |
Suggest actions for conversation messages |
graph LR
A[User selects text] --> B[TextClassifierManager]
B --> C[TextClassifierService]
C --> D{Entity Type}
D -->|Phone| E[Dial action]
D -->|Address| F[Map action]
D -->|Email| G[Compose action]
D -->|URL| H[Browse action]
D -->|DateTime| I[Calendar action]
50.7.4 AppPredictionManager¶
The App Prediction service predicts which apps the user will use next:
// frameworks/base/core/java/android/app/prediction/AppPredictionManager.java
@SystemApi
public final class AppPredictionManager {
public AppPredictor createAppPredictionSession(
@NonNull AppPredictionContext predictionContext) {
return new AppPredictor(mContext, predictionContext);
}
}
The AppPredictor provides ranked lists of apps based on context (time of
day, location, recent usage patterns). Launchers use this to order the app
drawer and populate suggestions.
50.7.5 TextClassifierService Manifest and Interface¶
// frameworks/base/core/java/android/service/textclassifier/TextClassifierService.java
@SystemApi
public abstract class TextClassifierService extends Service {
public static final String SERVICE_INTERFACE =
"android.service.textclassifier.TextClassifierService";
Manifest registration:
<service android:name=".YourTextClassifierService"
android:permission="android.permission.BIND_TEXTCLASSIFIER_SERVICE">
<intent-filter>
<action android:name="android.service.textclassifier.TextClassifierService" />
</intent-filter>
</service>
The system's default implementation is configured via
config_defaultTextClassifierPackage. If unset, a local
TextClassifierImpl runs in the calling app's process.
50.7.6 Text Classification Flow¶
sequenceDiagram
participant App
participant TCManager as TextClassificationManager
participant TCMS as TextClassificationManagerService
participant TCSvc as TextClassifierService
App->>TCManager: classifyText(text, options)
TCManager->>TCMS: Binder IPC
TCMS->>TCSvc: onClassifyText(sessionId, request, callback)
TCSvc->>TCSvc: Run ML model (entity recognition)
TCSvc-->>TCMS: TextClassification result
TCMS-->>TCManager: TextClassification result
TCManager-->>App: TextClassification (entities, actions, confidence)
The TextClassification result includes:
- Entity type (phone, email, address, URL, datetime, flight number)
- Confidence score
- Suggested
RemoteActionobjects for each entity - Language detection results
50.7.7 Content Capture Event Batching¶
The Content Capture system optimizes for performance through event batching:
graph LR
A["View Change<br/>Event"] --> B["Buffer<br/>(per session)"]
C["View Change<br/>Event"] --> B
D["View Change<br/>Event"] --> B
B -->|"Buffer full<br/>or timeout"| E["Flush"]
E --> F["ContentCaptureManagerService"]
F --> G["ContentCaptureService"]
Events are buffered per ContentCaptureSession and flushed:
- When the buffer reaches capacity
- When a timeout expires
- When the session ends
- When the activity pauses or stops
FlushMetrics provides statistics about the batching:
50.7.8 Content Capture and Data Sharing¶
The DataShareCallback and DataShareReadAdapter support sharing captured
content with external analytics while preserving privacy:
frameworks/base/core/java/android/service/contentcapture/
DataShareCallback.java
DataShareReadAdapter.java
IDataShareCallback.aidl
IDataShareReadAdapter.aidl
Data sharing uses file descriptors and pipe-based transfer to avoid copying sensitive content through shared memory.
50.7.9 Content Protection¶
A separate IContentProtectionService interface supports content protection
use cases (detecting and redacting sensitive content):
frameworks/base/core/java/android/service/contentcapture/
IContentProtectionService.aidl
IContentProtectionAllowlistCallback.aidl
50.7.10 The Intelligence Pipeline¶
These three services form a coherent pipeline:
graph TB
subgraph "Capture Layer"
CC[ContentCaptureService]
end
subgraph "Understanding Layer"
TC[TextClassifierService]
NER[Named Entity Recognition]
end
subgraph "Prediction Layer"
AP[AppPredictionService]
RANKING[Usage Ranking Model]
end
subgraph "Consumer Layer"
LAUNCHER[Launcher]
AUTOFILL[Autofill]
SHARE[Share Sheet]
SMARTLINK[Smart Linkify]
end
CC --> TC
CC --> AP
TC --> NER
AP --> RANKING
NER --> SMARTLINK
NER --> AUTOFILL
RANKING --> LAUNCHER
RANKING --> SHARE
50.7.11 AppPrediction Context¶
The AppPredictionContext configures what kind of predictions are requested:
// frameworks/base/core/java/android/app/prediction/AppPredictionManager.java
@SystemApi
public final class AppPredictionManager {
@NonNull
public AppPredictor createAppPredictionSession(
@NonNull AppPredictionContext predictionContext) {
return new AppPredictor(mContext, predictionContext);
}
}
The prediction context specifies:
- UI surface: Where predictions will be displayed (launcher, share sheet)
- Prediction count: How many predictions to return
- Package name: The app requesting predictions
- Extras: Additional context-specific parameters
50.7.12 Privacy Architecture for Intelligence Services¶
All three services share a common privacy model:
graph TB
subgraph "Privacy Guarantees"
A["Trusted System Component<br/>(cannot be changed by user)"]
B["Process Isolation<br/>(separate process)"]
C["CDD Requirements<br/>(OEM attestation)"]
D["On-Device Only<br/>(no cloud upload)"]
E["User Control<br/>(global disable via Settings)"]
end
A --> F["Intelligence Service"]
B --> F
C --> F
D --> F
E --> F
The CDD (Compatibility Definition Document) requires that:
- The intelligence service cannot transmit captured data off-device
- The service must respect user's privacy settings
- The service must be declared by the device manufacturer
- Third-party apps cannot replace the intelligence service
50.8 AppSearch¶
AppSearch is AOSP's on-device full-text search engine, delivered as a Mainline module. It underpins the AppFunctions discovery mechanism and provides structured data indexing for any app.
Source tree:
packages/modules/AppSearch/
framework/java/android/app/appsearch/
AppSearchManager.java -- System service entry point
AppSearchSession.java -- Per-database session
GenericDocument.java -- Base document type
SearchSpec.java -- Query specification
SetSchemaRequest.java -- Schema definition
...
service/java/com/android/server/appsearch/
AppSearchManagerService.java -- System server
external/localstorage/
AppSearchImpl.java -- Local storage engine
50.8.1 Architecture¶
graph TB
subgraph "App Process"
APP[Application]
ASM[AppSearchManager]
SESS[AppSearchSession]
end
subgraph "AppSearch Module"
ASMS[AppSearchManagerService]
IMPL["AppSearchImpl<br/>IcingSearchEngine"]
INDEX[Full-Text Index]
SCHEMA[Schema Store]
end
APP --> ASM
ASM -- "Binder IPC" --> ASMS
ASM --> SESS
SESS -- "CRUD operations" --> ASMS
ASMS --> IMPL
IMPL --> INDEX
IMPL --> SCHEMA
50.8.2 Core Concepts¶
From the AppSearchManager Javadoc:
// packages/modules/AppSearch/framework/java/android/app/appsearch/AppSearchManager.java
// AppSearch is an offline, on-device search library for managing structured
// data featuring:
// - APIs to index and retrieve data via full-text search
// - An API for applications to explicitly grant read-access permission of
// their data to other applications
// - An API for applications to opt into or out of having their data displayed
// on System UI surfaces
Key abstractions:
| Concept | Description |
|---|---|
| Database | Isolated per-app search namespace, created via SearchContext |
| Schema | Defines document types and their properties (like SQL DDL) |
| GenericDocument | A document instance with namespace, ID, properties, and score |
| SearchSpec | Query parameters: text query, filters, ranking strategy |
| Visibility | Per-schema access control for cross-app search |
50.8.3 Schema Definition¶
AppSearchSchema emailSchemaType = new AppSearchSchema.Builder("Email")
.addProperty(new StringPropertyConfig.Builder("subject")
.setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
.setIndexingType(PropertyConfig.INDEXING_TYPE_PREFIXES)
.setTokenizerType(PropertyConfig.TOKENIZER_TYPE_PLAIN)
.build()
).build();
50.8.4 Document Indexing¶
GenericDocument email = new GenericDocument.Builder<>(NAMESPACE, ID, "Email")
.setPropertyString("subject", EMAIL_SUBJECT)
.setScore(EMAIL_SCORE)
.build();
PutDocumentsRequest request = new PutDocumentsRequest.Builder()
.addGenericDocuments(email)
.build();
session.put(request, executor, callback);
50.8.5 Search¶
SearchSpec spec = new SearchSpec.Builder()
.addFilterSchemas("Email")
.setRankingStrategy(SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE)
.build();
SearchResults results = session.search("important meeting", spec);
50.8.6 The IcingSearchEngine¶
Under the hood, AppSearch is backed by the IcingSearchEngine, a C++ library that provides:
- Full-text indexing with BM25F scoring
- Prefix matching
- Namespace-based isolation
- Integer and document-level indexing
- Query syntax with boolean operators
50.8.7 Visibility and Access Control¶
AppSearch enforces visibility at the schema level:
SetSchemaRequest.Builder builder = new SetSchemaRequest.Builder();
builder.addSchemas(emailSchemaType);
builder.setSchemaTypeVisibilityForPackage(
"Email",
/* visible= */ true,
new PackageIdentifier("com.example.reader", sigDigest));
builder.setSchemaTypeDisplayedBySystem("Email", /* displayed= */ true);
Three visibility levels:
- Package visibility: Specific packages can read documents of a type
- System visibility: System-designated querier can access for system UI
- Self-only: Default, only the indexing app can query
50.8.8 Global Search¶
Apps with the READ_GLOBAL_APP_SEARCH_DATA permission (typically system apps)
can search across all packages' visible data:
graph TB
subgraph "App A Database"
A_EMAILS[Email documents]
A_CONTACTS[Contact documents]
end
subgraph "App B Database"
B_NOTES[Note documents]
B_TASKS[Task documents]
end
subgraph "AppSearch Service"
INDEX["Unified Index<br/>(IcingSearchEngine)"]
VIS[Visibility Filter]
end
subgraph "System App"
QUERIER["Global Search<br/>Querier"]
end
A_EMAILS --> INDEX
A_CONTACTS --> INDEX
B_NOTES --> INDEX
B_TASKS --> INDEX
QUERIER --> VIS
VIS --> INDEX
50.8.9 AppSearch and AppFunctions Integration¶
When AppFunctions indexes function metadata, it creates documents of type
AppFunctionStaticMetadata in AppSearch. Agents discover functions by:
- Opening a global search session
- Querying for
AppFunctionStaticMetadatadocuments - Extracting
functionIdentifierand schema information - Using these to construct
ExecuteAppFunctionRequest
sequenceDiagram
participant Agent as AI Agent
participant AS as AppSearch
participant AFM as AppFunctionManager
Agent->>AS: search("CreateNote", AppFunctionStaticMetadata)
AS-->>Agent: doc with functionId and schema
Agent->>AFM: executeAppFunction(targetPkg, functionId)
AFM-->>Agent: ExecuteAppFunctionResponse
50.8.10 AppSearch Query Syntax¶
AppSearch supports a rich query language:
| Feature | Example | Description |
|---|---|---|
| Full-text | "important meeting" |
Match documents containing these terms |
| Boolean AND | term1 AND term2 |
Both terms must match |
| Boolean OR | term1 OR term2 |
Either term matches |
| Negation | NOT term |
Exclude documents with term |
| Prefix | meet* |
Prefix matching |
| Property restrict | subject:meeting |
Match in specific property |
| Semantic search | semanticSearch(...) |
Vector similarity search |
The AST (Abstract Syntax Tree) for queries is represented by node classes:
packages/modules/AppSearch/framework/java/external/android/app/appsearch/ast/
FunctionNode.java
NegationNode.java
query/SearchNode.java
query/SemanticSearchNode.java
query/HasPropertyNode.java
operators/ComparatorNode.java
operators/PropertyRestrictNode.java
50.8.11 GenericDocument Deep Dive¶
The GenericDocument is the foundational data type shared between AppSearch
and AppFunctions:
// packages/modules/AppSearch/framework/java/external/android/app/appsearch/GenericDocument.java
GenericDocument doc = new GenericDocument.Builder<>(namespace, id, schemaType)
.setPropertyString("name", "John")
.setPropertyLong("age", 30)
.setPropertyDouble("score", 0.95)
.setPropertyBoolean("active", true)
.setPropertyBytes("avatar", imageBytes)
.setPropertyDocument("address", addressDoc)
.setScore(100)
.setTtlMillis(TimeUnit.DAYS.toMillis(30))
.setCreationTimestampMillis(System.currentTimeMillis())
.build();
Properties support multiple cardinalities:
CARDINALITY_REQUIRED-- Exactly one valueCARDINALITY_OPTIONAL-- Zero or one valueCARDINALITY_REPEATED-- Zero or more values
50.8.12 AppSearchImpl and IcingSearchEngine¶
The AppSearchImpl class wraps the native IcingSearchEngine:
packages/modules/AppSearch/service/java/com/android/server/appsearch/
external/localstorage/AppSearchImpl.java
IcingSearchEngine provides:
- BM25F scoring for relevance ranking
- Inverted index for fast full-text search
- Forward index for property retrieval
- Namespace-based isolation
- TTL-based automatic document expiry
- Schema migration support
50.8.13 Observer API¶
Apps can register observers to be notified of changes:
// AppSearchManager observer
appSearchManager.registerObserverCallback(
"com.example.app",
new ObserverSpec.Builder().addFilterSchemas("Email").build(),
executor,
new ObserverCallback() {
@Override
public void onSchemaChanged(SchemaChangeInfo info) { ... }
@Override
public void onDocumentChanged(DocumentChangeInfo info) { ... }
});
This is how the AppFunctions system monitors for metadata changes -- the
service registers an observer in AppSearch and reacts to
AppFunctionStaticMetadata document changes.
50.8.14 IcingSearchEngine Internals¶
AppSearchImpl wraps the native IcingSearchEngine through a JNI boundary.
The engine provides a complete search stack implemented in C++:
// packages/modules/AppSearch/service/java/com/android/server/appsearch/
// external/localstorage/AppSearchImpl.java
@WorkerThread
public final class AppSearchImpl implements Closeable {
@GuardedBy("mReadWriteLock")
IcingSearchEngineInterface mIcingSearchEngineLocked;
// Thread safety: ReadWriteLock separating query (READ) from mutation (WRITE)
private final ReadWriteLock mReadWriteLock = new ReentrantReadWriteLock();
// Caches for performance
private final SchemaCache mSchemaCacheLocked = new SchemaCache();
private final NamespaceCache mNamespaceCacheLocked = new NamespaceCache();
private volatile DocumentLimiter mDocumentLimiterLocked;
}
Prefix-Based Isolation:
AppSearchImpl achieves per-package, per-database isolation within a single
IcingSearchEngine instance by prefixing all schema types, namespaces, and
document IDs:
graph TB
subgraph "App A, Database 'mail'"
A_TYPE["Schema: Email"]
A_NS["Namespace: inbox"]
A_DOC["Doc ID: msg123"]
end
subgraph "IcingSearchEngine (physical storage)"
I_TYPE["Schema: com.app.a$mail/Email"]
I_NS["Namespace: com.app.a$mail/inbox"]
I_DOC["Doc ID: com.app.a$mail/inbox#msg123"]
end
subgraph "App B, Database 'notes'"
B_TYPE["Schema: Note"]
B_NS["Namespace: personal"]
B_DOC["Doc ID: note456"]
end
subgraph "IcingSearchEngine (same instance)"
J_TYPE["Schema: com.app.b$notes/Note"]
J_NS["Namespace: com.app.b$notes/personal"]
J_DOC["Doc ID: com.app.b$notes/personal#note456"]
end
A_TYPE -->|"addPrefix()"| I_TYPE
A_NS -->|"addPrefix()"| I_NS
A_DOC -->|"addPrefix()"| I_DOC
B_TYPE -->|"addPrefix()"| J_TYPE
B_NS -->|"addPrefix()"| J_NS
B_DOC -->|"addPrefix()"| J_DOC
When retrieving results, removePrefix() and removePrefixesFromDocument()
strip the prefix so callers never see the internal naming.
Converter Layer:
A set of converter classes translate between the Java AppSearch API types and Icing protobuf types:
| Converter | Direction |
|---|---|
GenericDocumentToProtoConverter |
GenericDocument <-> DocumentProto |
SchemaToProtoConverter |
AppSearchSchema <-> SchemaTypeConfigProto |
SearchSpecToProtoConverter |
SearchSpec <-> SearchSpecProto + ScoringSpecProto + ResultSpecProto |
SearchResultToProtoConverter |
SearchResultProto -> SearchResult |
SetSchemaResponseToProtoConverter |
SetSchemaResultProto -> SetSchemaResponse |
BlobHandleToProtoConverter |
AppSearchBlobHandle <-> BlobProto |
Scoring and Ranking:
IcingSearchEngine supports multiple ranking strategies:
| Strategy | Description |
|---|---|
RANKING_STRATEGY_RELEVANCE_SCORE |
BM25F text relevance |
RANKING_STRATEGY_CREATION_TIMESTAMP |
Newest first |
RANKING_STRATEGY_DOCUMENT_SCORE |
App-provided score |
RANKING_STRATEGY_USAGE_COUNT |
Number of usage reports |
RANKING_STRATEGY_USAGE_LAST_USED_TIMESTAMP |
Most recently used |
RANKING_STRATEGY_JOIN_AGGREGATE_SCORE |
Score from joined docs |
BM25F (Best Matching 25 with Field weighting) is the default relevance algorithm. It considers term frequency, inverse document frequency, and document length normalisation across indexed properties with configurable field weights.
Optimization:
AppSearchImpl periodically optimises the Icing index:
@VisibleForTesting static final int CHECK_OPTIMIZE_INTERVAL = 100;
// After every 100 mutations, check GetOptimizeInfoResult
// If significant space can be reclaimed, run optimize()
Optimisation compacts the index, removing tombstoned documents and rebuilding internal data structures.
50.8.15 Schema Management Deep Dive¶
Schema management is a critical concern because schema changes can break
existing documents. AppSearchImpl.setSchema() handles migrations:
sequenceDiagram
participant App
participant ASMS as AppSearchManagerService
participant Impl as AppSearchImpl
participant Icing as IcingSearchEngine
App->>ASMS: setSchema(SetSchemaRequest)
ASMS->>Impl: setSchema(prefix, schemas, visibilityConfigs)
Impl->>Impl: Add prefix to all schema types
Impl->>Icing: setSchema(SchemaProto, forceOverride?)
alt Compatible change (add optional property)
Icing-->>Impl: SUCCESS
Impl->>Impl: Update SchemaCache
else Incompatible change (remove required property)
Icing-->>Impl: SetSchemaResult with incompatibleTypes
Impl-->>App: SetSchemaResponse with migrationTypes
Note over App: App provides Migrator to transform docs
end
Incompatible schema changes include:
- Removing a property
- Changing cardinality from OPTIONAL to REQUIRED
- Changing property data type
- Changing indexing type on an existing property
For each incompatible type, the app can provide a Migrator that transforms
documents from the old schema to the new one.
50.8.16 Visibility Store Architecture¶
The VisibilityStore manages per-schema access control within
AppSearchImpl:
packages/modules/AppSearch/service/java/com/android/server/appsearch/
external/localstorage/visibilitystore/
VisibilityStore.java -- Stores visibility configs
VisibilityChecker.java -- Interface for permission checks
VisibilityUtil.java -- Resolution logic
CallerAccess.java -- Encapsulates caller identity
VisibilityToDocumentConverter.java -- Persists configs as documents
VisibilityStoreMigrationHelperFromV0.java -- V0 -> V1 migration
VisibilityStoreMigrationHelperFromV1.java -- V1 -> V2 migration
Visibility is stored as AppSearch documents themselves, using a special
internal database. When a global search query is executed, VisibilityUtil
filters results by checking:
-
Package visibility -- Is the querying package in the schema's allowed package list, verified by signature digest?
-
System visibility -- Does the querier hold the role/permission designated for system UI access?
-
Self-access -- Is the querier the same package that indexed the schema?
graph TB
Q["Global Search Query"]
Q --> VU["VisibilityUtil.isSchemaSearchableByCaller()"]
VU --> C1{"Same package?"}
C1 -->|"Yes"| ALLOW["Allow"]
C1 -->|"No"| C2{"Package in<br/>visibility list?"}
C2 -->|"Yes, signature matches"| ALLOW
C2 -->|"No"| C3{"System querier<br/>with permission?"}
C3 -->|"Yes"| C4{"Schema displayed<br/>by system?"}
C4 -->|"Yes"| ALLOW
C4 -->|"No"| DENY["Deny"]
C3 -->|"No"| DENY
50.8.17 Blob Storage¶
AppSearch supports storing binary large objects (BLOBs) alongside documents
through AppSearchBlobHandle:
// AppSearchImpl wraps IcingSearchEngine's blob support:
// - BlobProto for storage
// - BlobHandleToProtoConverter for conversion
// - NamespaceBlobStorageInfoProto for storage statistics
BLOBs are stored in a dedicated directory (mBlobFilesDir) separate from
the index, with ParcelFileDescriptor used for efficient transfer across
process boundaries.
50.8.18 Thread Safety and Locking Model¶
AppSearchImpl uses a ReentrantReadWriteLock to achieve high query
throughput while maintaining data consistency:
graph TB
subgraph "READ Lock (concurrent)"
Q1["search()"]
Q2["getDocument()"]
Q3["getSchema()"]
Q4["getStorageInfo()"]
Q5["getNamespaces()"]
end
subgraph "WRITE Lock (exclusive)"
W1["setSchema()"]
W2["putDocument()"]
W3["remove()"]
W4["removeByQuery()"]
W5["optimize()"]
W6["reset()"]
W7["close()"]
end
RWL["ReentrantReadWriteLock"]
Q1 --> RWL
Q2 --> RWL
Q3 --> RWL
W1 --> RWL
W2 --> RWL
W5 --> RWL
All read operations (queries, document retrieval, schema inspection) run
concurrently under the READ lock. All mutating operations (schema changes,
document puts/deletes, optimisation) require the exclusive WRITE lock. The
@WorkerThread annotation enforces that no AppSearch operations run on the
main thread.
50.8.19 Document Lifecycle and TTL¶
Documents in AppSearch have a configurable time-to-live:
GenericDocument doc = new GenericDocument.Builder<>(namespace, id, schemaType)
.setTtlMillis(TimeUnit.DAYS.toMillis(30)) // Expire after 30 days
.setCreationTimestampMillis(System.currentTimeMillis())
.build();
IcingSearchEngine enforces TTL by:
- Recording
creationTimestampMillis+ttlMillisas the expiry time - During
optimize(), deleting documents past their expiry - Excluding expired documents from search results even before optimisation
A TTL of 0 means the document never expires (default).
50.8.20 Join Queries¶
AppSearch supports join queries that combine results from two schema types:
JoinSpec joinSpec = new JoinSpec.Builder("referencedPropertyName")
.setNestedSearch("childQuery", new SearchSpec.Builder().build())
.setAggregationScoringStrategy(
JoinSpec.AGGREGATION_SCORING_RESULT_COUNT)
.build();
SearchSpec spec = new SearchSpec.Builder()
.setJoinSpec(joinSpec)
.build();
Join queries enable patterns like "find emails with the most attachments" or "find contacts with recent messages":
graph LR
subgraph "Parent Documents"
P1["Email {id: e1}"]
P2["Email {id: e2}"]
end
subgraph "Child Documents"
C1["Attachment {emailRef: e1}"]
C2["Attachment {emailRef: e1}"]
C3["Attachment {emailRef: e2}"]
end
C1 -->|"referencedPropertyName"| P1
C2 -->|"referencedPropertyName"| P1
C3 -->|"referencedPropertyName"| P2
subgraph "Join Result"
R1["Email e1 (score: 2 attachments)"]
R2["Email e2 (score: 1 attachment)"]
end
50.8.21 AppSearchManagerService -- The System Server Layer¶
AppSearchManagerService is the system_server component that mediates all
AppSearch access:
// packages/modules/AppSearch/service/java/com/android/server/appsearch/
// AppSearchManagerService.java
It handles:
-
Per-user instances: Maintains separate
AppSearchImplinstances per user profile -
Permission enforcement: Validates caller identity and permissions before delegating to
AppSearchImpl -
Rate limiting: Enforces API call quotas per-package
- Statistics collection: Gathers
InitializeStats,PutDocumentStats,QueryStats,SetSchemaStats,RemoveStats,OptimizeStatsfor performance monitoring
The statistics pipeline tracks:
| Stat Class | Measures |
|---|---|
InitializeStats |
Engine initialisation time, document count |
PutDocumentStats |
Indexing latency, document size |
QueryStats |
Query latency, result count, ranking time |
SetSchemaStats |
Schema migration time, incompatible changes |
RemoveStats |
Deletion latency |
OptimizeStats |
Optimisation duration, space reclaimed |
PersistToDiskStats |
Flush latency |
50.9 AdServices¶
The AdServices Mainline module provides privacy-preserving advertising APIs as part of the Privacy Sandbox initiative. While primarily advertising-focused, the underlying technology demonstrates key on-device ML patterns.
Source tree:
packages/modules/AdServices/
adservices/
framework/java/android/adservices/
topics/TopicsManager.java -- Topics API
customaudience/CustomAudienceManager.java -- FLEDGE/Protected Audiences
service-core/java/com/android/adservices/service/
topics/TopicsWorker.java -- On-device topic classification
service/ -- Main service
sdksandbox/ -- SDK Runtime sandbox
50.9.1 Architecture¶
graph TB
subgraph "App / SDK"
APP[App or Ad SDK]
TM[TopicsManager]
CAM[CustomAudienceManager]
end
subgraph "AdServices Module"
TS[Topics Service]
TW[TopicsWorker]
CLASSIFIER[On-Device Classifier]
PA["Protected Audiences<br/>FLEDGE"]
MODEL["ML Model<br/>App-to-Topic mapping"]
end
subgraph "SDK Sandbox"
SDK[Sandboxed SDK Runtime]
end
APP --> TM
APP --> CAM
TM -- "Binder" --> TS
CAM -- "Binder" --> PA
TS --> TW
TW --> CLASSIFIER
CLASSIFIER --> MODEL
APP --> SDK
50.9.2 Topics API¶
The Topics API classifies apps into interest categories using an on-device ML classifier:
// packages/modules/AdServices/adservices/framework/java/
// android/adservices/topics/TopicsManager.java
@RequiresApi(Build.VERSION_CODES.S)
public final class TopicsManager {
@RequiresPermission(ACCESS_ADSERVICES_TOPICS)
public void getTopics(
@NonNull GetTopicsRequest getTopicsRequest,
@NonNull @CallbackExecutor Executor executor,
@NonNull OutcomeReceiver<GetTopicsResponse, Exception> callback) {
The classifier runs entirely on-device:
- The system downloads a taxonomy of ~470 topics
- An ML model maps app package names to topic categories
- Each epoch (~1 week), the system records which topics the user's apps map to
- When an SDK calls
getTopics(), it receives a privacy-safe selection of topics with noise added
50.9.3 Protected Audiences (FLEDGE)¶
Protected Audiences runs ad auctions on-device:
sequenceDiagram
participant Buyer as Ad Buyer SDK
participant CAM as CustomAudienceManager
participant Service as AdServices
participant Seller as Ad Seller
Buyer->>CAM: joinCustomAudience(audience)
Note over Service: Audience stored on-device
Seller->>Service: selectAds(adSelectionConfig)
Service->>Service: Run bidding logic (JavaScript in sandbox)
Service->>Service: Run scoring logic
Service-->>Seller: AdSelectionOutcome
50.9.4 SDK Sandbox¶
AdServices introduced the SDK Runtime sandbox:
packages/modules/AdServices/sdksandbox/
framework/ -- SDK sandbox framework
SdkSandbox/ -- Sandbox process
service/ -- Sandbox service
Third-party SDKs run in a separate process with restricted permissions, preventing unauthorized data collection.
50.9.5 Topics Classification Pipeline¶
The on-device topics classifier follows this pipeline:
graph TB
A[App Usage Data] --> B["Epoch Computation<br/>Weekly"]
B --> C{"For each app used<br/>this epoch"}
C --> D["ML Classifier<br/>App -> Topics mapping"]
D --> E["User Interest Topics<br/>for this epoch"]
E --> F["Store Top Topics<br/>Last 3 epochs"]
G["getTopics() API call"] --> H{Random selection}
H --> I["Return 1 topic<br/>from past epoch"]
H --> J["Return random topic<br/>(5% noise)"]
The classifier uses a pre-trained ML model that maps app package names to a fixed taxonomy of approximately 470 topics. The model is downloaded and updated through the AdServices module.
Privacy mechanisms:
- Epoch-based: Topics are computed weekly, not per-access
- Top-K selection: Only the top topics per epoch are stored
- Random noise: 5% of returned topics are random
- Per-caller isolation: Different SDKs see different topic selections
- User controls: Users can view and remove topics in Settings
50.9.6 Protected Audiences (FLEDGE) Deep Dive¶
The Protected Audiences API runs a full ad auction on-device:
graph TB
subgraph "Buyer Phase"
B1["Custom Audience 1<br/>from Buyer A"]
B2["Custom Audience 2<br/>from Buyer B"]
BID1["generateBid.js<br/>Buyer A"]
BID2["generateBid.js<br/>Buyer B"]
end
subgraph "Seller Phase"
SCORE["scoreAd.js<br/>Seller"]
REPORT["reportResult.js<br/>Reporting"]
end
subgraph "On-Device Auction"
AUCTION[Ad Selection Engine]
end
B1 --> BID1
B2 --> BID2
BID1 --> AUCTION
BID2 --> AUCTION
AUCTION --> SCORE
SCORE --> REPORT
REPORT --> WINNER[Winning Ad]
Key components:
- Custom Audience: User interest group, stored on-device
- Bidding Logic: JavaScript functions that run in a sandboxed environment
- Scoring Logic: Seller-provided JavaScript that ranks bids
- Reporting: Privacy-preserving impression reporting
All JavaScript execution happens in a sandboxed environment with no network access during the auction. This prevents information leakage between the bidding and scoring phases.
50.9.7 Attribution Reporting¶
AdServices includes attribution reporting that links ad impressions to conversions while preserving privacy:
sequenceDiagram
participant Publisher as Publisher App
participant AdServices as AdServices Module
participant Advertiser as Advertiser App
Publisher->>AdServices: registerSource(impression)
Note over AdServices: Store impression locally
Advertiser->>AdServices: registerTrigger(conversion)
Note over AdServices: Match with stored impression
AdServices->>AdServices: Apply privacy noise
AdServices->>AdServices: Schedule delayed report
AdServices-->>Publisher: Aggregated report (after delay)
50.9.8 AdServices Module Structure¶
packages/modules/AdServices/
adservices/
framework/ -- Public APIs (Topics, FLEDGE, Attribution)
service-core/ -- Core service logic
service/ -- System service
libraries/ -- Shared libraries
clients/ -- Client libraries for callers
flags/ -- Feature flags
sdksandbox/
framework/ -- SDK Runtime APIs
SdkSandbox/ -- Sandbox implementation
service/ -- Sandbox system service
apex/ -- APEX module packaging
50.9.9 Comparison of AI Privacy Mechanisms¶
A comparison of privacy approaches across AOSP AI subsystems:
graph TB
subgraph "Process Isolation"
ODI["OnDeviceIntelligence<br/>isolatedProcess=true"]
FC["Federated Compute<br/>IsolatedTrainingService"]
SDK["SDK Sandbox<br/>SdkSandbox"]
end
subgraph "Data Minimization"
TOPICS["Topics API<br/>K-anonymity + noise"]
FLEDGE["FLEDGE<br/>On-device auction"]
ATTR["Attribution<br/>Aggregation + delay"]
end
subgraph "Access Control"
AF["AppFunctions<br/>Allowlist + permissions"]
CC["Computer Control<br/>User approval + target restriction"]
CAP["Content Capture<br/>System-only + allowlist"]
end
| Subsystem | Isolation | Encryption | Noise | User Consent |
|---|---|---|---|---|
| OnDeviceIntelligence | Process | N/A | N/A | Permission |
| Federated Compute | Process | Secure aggregation | Differential privacy | N/A |
| Topics API | N/A | N/A | 5% random | Settings |
| FLEDGE | JavaScript sandbox | N/A | N/A | Opt-out |
| AppFunctions | N/A | N/A | N/A | Permission + allowlist |
| Computer Control | Virtual display | N/A | N/A | Per-session user approval |
| Content Capture | Process | N/A | N/A | Global toggle |
50.9.10 Topics API Classification Pipeline Deep Dive¶
The Topics classification pipeline is orchestrated by EpochManager, which
runs epoch computation as a scheduled job. The complete data flow from app
usage to topic delivery involves several key classes:
packages/modules/AdServices/adservices/service-core/java/com/android/adservices/service/topics/
TopicsWorker.java -- API implementation, thread-safe singleton
EpochManager.java -- Epoch computation orchestrator
CacheManager.java -- In-memory topic cache
BlockedTopicsManager.java -- User-blocked topics
AppUpdateManager.java -- App install/uninstall handling
EncryptionManager.java -- Topic encryption for transport
classifier/
Classifier.java -- Classification interface
ClassifierManager.java -- Classifier selection
OnDeviceClassifier.java -- TFLite BERT-based classifier
PrecomputedClassifier.java -- Lookup-table classifier
ModelManager.java -- ML model lifecycle
ClassifierInputManager.java -- Input preprocessing
Preprocessor.java -- Text preprocessing
EpochManager -- The Computation Engine:
EpochManager maintains a database of per-epoch computations:
// packages/modules/AdServices/adservices/service-core/java/com/android/adservices/service/topics/
// EpochManager.java
public class EpochManager {
// Tables tracked for garbage collection:
// - AppClassificationTopicsContract -- app -> topics mapping per epoch
// - TopTopicsContract -- top topics per epoch
// - ReturnedTopicContract -- topics returned to callers
// - UsageHistoryContract -- SDK usage per epoch
// - AppUsageHistoryContract -- app usage per epoch
// - TopicContributorsContract -- which apps contributed to each topic
}
Epoch Computation Flow:
sequenceDiagram
participant JM as EpochJobService
participant EM as EpochManager
participant CM as ClassifierManager
participant OD as OnDeviceClassifier
participant DB as TopicsDao
JM->>EM: processEpoch()
EM->>DB: getAppsUsedInEpoch(currentEpoch)
DB-->>EM: Set<AppInfo>
EM->>CM: classify(appPackageNames)
CM->>OD: classify(apps)
Note over OD: BertNLClassifier.classify()<br/>Maps package name -> topic IDs
OD-->>CM: Map<App, List<Topic>>
CM-->>EM: appClassificationTopics
EM->>EM: computeTopTopics(appTopics, numTopTopics=5, numRandom=1)
Note over EM: Count topic frequency across apps<br/>Select top-5 by frequency<br/>Add 1 random topic as noise
EM->>DB: persistTopTopics(epoch, topTopics)
EM->>DB: persistAppClassificationTopics(epoch, appTopics)
EM->>DB: persistTopicContributors(epoch, contributorMap)
EM->>EM: garbageCollectOldEpochs()
Note over EM: Remove data older than<br/>lookBackEpochs (default: 3)
Dual Classifier Strategy:
The ClassifierManager supports two classifiers and selects based on
configuration:
graph TB
CM["ClassifierManager"]
CM -->|"Flag: ON_DEVICE"| OD["OnDeviceClassifier<br/>TFLite BERT model"]
CM -->|"Flag: PRECOMPUTED"| PC["PrecomputedClassifier<br/>Server-side lookup table"]
CM -->|"Flag: BOTH"| BOTH["Run both,<br/>merge results"]
OD --> BERT["BertNLClassifier<br/>(TFLite Task Library)"]
BERT --> MODEL["Downloaded TFLite Model"]
PC --> TABLE["Precomputed<br/>App -> Topic Map"]
TABLE --> ASSET["Downloaded from server"]
The on-device classifier uses TensorFlow Lite's BertNLClassifier:
// packages/modules/AdServices/adservices/service-core/java/com/android/adservices/service/topics/
// classifier/OnDeviceClassifier.java
public class OnDeviceClassifier implements Classifier {
private BertNLClassifier mBertNLClassifier; // TFLite BERT model
private ImmutableList<Integer> mLabels; // Topic ID label set
// classify() preprocesses app info, runs inference,
// maps output categories to Topic IDs
}
The model and labels are managed by ModelManager, which downloads assets
from the server and tracks version information. The ClassifierInputManager
and Preprocessor prepare app metadata (package name, app title,
description) as input text for the BERT model.
Topic Delivery with Privacy:
When TopicsManager.getTopics() is called:
sequenceDiagram
participant SDK as Ad SDK
participant TW as TopicsWorker
participant CM as CacheManager
participant BM as BlockedTopicsManager
participant EM as EncryptionManager
SDK->>TW: getTopics(request)
TW->>TW: Acquire READ lock
TW->>CM: getTopicsForCaller(sdkName, epoch-1..epoch-3)
CM->>CM: For each past epoch:<br/>1. Get top topics<br/>2. Select topic assigned to this SDK<br/>3. Apply 5% random substitution
CM-->>TW: List<CombinedTopic>
TW->>BM: filterBlockedTopics(topics)
BM-->>TW: filteredTopics
TW->>EM: encryptTopics(filteredTopics)
Note over EM: HpkeEncrypter encrypts<br/>each topic for transport
EM-->>TW: List<EncryptedTopic>
TW-->>SDK: GetTopicsResult(topics, encryptedTopics)
TopicsWorker Thread Safety:
TopicsWorker uses a ReentrantReadWriteLock to allow concurrent reads
while serialising writes:
| Operation | Lock |
|---|---|
getTopics() |
READ |
processEpoch() |
WRITE |
handleAppUninstallation() |
WRITE |
loadCache() |
WRITE |
50.9.11 Protected Audiences Auction Architecture¶
The Protected Audiences (FLEDGE) auction is implemented through a multi-phase pipeline that executes JavaScript in a sandboxed environment:
graph TB
subgraph "Phase 1: Custom Audience Management"
JOIN["joinCustomAudience()"]
STORE["On-Device Storage"]
FETCH["BackgroundFetchRunner<br/>Daily update"]
end
subgraph "Phase 2: Auction Preparation"
SEL["selectAds(AdSelectionConfig)"]
BUYERS["Fetch buyer bidding signals"]
SELLER_S["Fetch seller scoring signals"]
end
subgraph "Phase 3: Bidding (per buyer)"
GEN_BID["generateBid.js<br/>JavaScript in sandbox"]
CA_DATA["Custom Audience data"]
BID_SIG["Buyer signals"]
end
subgraph "Phase 4: Scoring"
SCORE_AD["scoreAd.js<br/>JavaScript in sandbox"]
SELLER_SIG["Seller signals"]
end
subgraph "Phase 5: Reporting"
REPORT_WIN["reportWin.js<br/>Winner notification"]
REPORT_RES["reportResult.js<br/>Seller notification"]
end
JOIN --> STORE
STORE --> FETCH
FETCH -->|"Update bidding logic,<br/>ads, signals"| STORE
SEL --> BUYERS
SEL --> SELLER_S
BUYERS --> GEN_BID
STORE --> CA_DATA
CA_DATA --> GEN_BID
BID_SIG --> GEN_BID
GEN_BID -->|"Bid + ad"| SCORE_AD
SELLER_S --> SELLER_SIG
SELLER_SIG --> SCORE_AD
SCORE_AD -->|"Winning ad"| REPORT_WIN
SCORE_AD --> REPORT_RES
Key service classes:
packages/modules/AdServices/adservices/service-core/java/com/android/adservices/service/
customaudience/
CustomAudienceServiceImpl.java -- joinCustomAudience / leaveCustomAudience
CustomAudienceImpl.java -- Core logic
BackgroundFetchRunner.java -- Daily update fetch
BackgroundFetchWorker.java -- Work scheduling
CustomAudienceValidator.java -- Input validation
CustomAudienceQuantityChecker.java -- Per-app audience limits
FetchCustomAudienceImpl.java -- Server-initiated audiences
Custom Audience Validation:
Before a custom audience is stored, it passes through a chain of validators:
| Validator | Check |
|---|---|
CustomAudienceNameValidator |
Name length and format |
CustomAudienceActivationTimeValidator |
Activation not in far future |
CustomAudienceExpirationTimeValidator |
Expiration within allowed range |
CustomAudienceBiddingLogicUriValidator |
HTTPS URI, correct authority |
CustomAudienceDailyUpdateUriValidator |
HTTPS URI for daily refresh |
CustomAudienceAdsValidator |
Ad render URIs and metadata |
CustomAudienceFieldSizeValidator |
Total size within limits |
CustomAudienceUserBiddingSignalsValidator |
Signal data format |
CustomAudienceQuantityChecker |
Per-app audience count limit |
Background Fetch Pipeline:
BackgroundFetchRunner periodically updates custom audience data:
sequenceDiagram
participant BFS as BackgroundFetchJobService
participant BFW as BackgroundFetchWorker
participant BFR as BackgroundFetchRunner
participant NET as Network
BFS->>BFW: Schedule daily job
BFW->>BFR: runBackgroundFetch()
loop For each Custom Audience
BFR->>NET: GET dailyUpdateUri
NET-->>BFR: Updated bidding logic, ads, signals
BFR->>BFR: Validate updated data
BFR->>BFR: Store updated Custom Audience
end
Note over BFR: Remove expired audiences
50.9.12 SDK Sandbox Architecture¶
The SDK Runtime sandbox isolates third-party advertising SDKs in a separate process:
packages/modules/AdServices/sdksandbox/
framework/java/android/app/sdksandbox/
SdkSandboxManager.java -- Public API for loading SDKs
SandboxedSdkProvider.java -- Base class for sandboxed SDKs
SandboxedSdkContext.java -- Restricted Context for SDK process
SandboxedSdk.java -- Handle to loaded SDK
LoadSdkException.java -- Error reporting
SharedPreferencesSyncManager.java -- App->SDK shared prefs sync
SdkSandbox/ -- Sandbox process implementation
service/ -- System service
SDK Loading Flow:
sequenceDiagram
participant App
participant SSM as SdkSandboxManager
participant SSS as SdkSandboxService
participant SBP as SandboxProcess
App->>SSM: loadSdk(sdkName, params)
SSM->>SSS: loadSdk(callingPackage, sdkName, params)
SSS->>SSS: Verify SDK is declared<br/>in app manifest
SSS->>SBP: Start/bind sandbox process
SBP->>SBP: Load SDK in isolated ClassLoader
SBP->>SBP: Create SandboxedSdkContext<br/>(restricted permissions)
SBP->>SBP: Call SandboxedSdkProvider.onLoadSdk()
SBP-->>SSS: SandboxedSdk handle
SSS-->>App: SandboxedSdk (via callback)
App->>SSM: requestSurfacePackage(sdk)
SSM->>SBP: Render UI in sandbox
SBP-->>App: SurfacePackage for embedding
SDK Sandbox Restrictions:
The SandboxedSdkContext imposes strict limits:
| Capability | Allowed |
|---|---|
| Network access | Limited (through AdServices APIs only) |
| Storage access | Isolated per-SDK directory |
| Content providers | Blocked |
| Broadcast receivers | Blocked |
| StartActivity | Blocked (no direct UI) |
| Shared preferences | Read-only sync from host app |
| UI rendering | Via SurfacePackage only |
This ensures that advertising SDKs cannot:
- Exfiltrate user data through side channels
- Access the host app's storage or databases
- Launch activities or services independently
- Fingerprint users through system APIs
50.9.13 AdServices Module Structure Deep Dive¶
graph TB
subgraph "APEX Module (com.android.adservices)"
subgraph "Framework Layer"
TM_F["TopicsManager"]
CAM_F["CustomAudienceManager"]
ATR_F["MeasurementManager<br/>(Attribution)"]
SSM_F["SdkSandboxManager"]
end
subgraph "Service Layer"
TS_S["TopicsServiceImpl"]
CAS_S["CustomAudienceServiceImpl"]
ADS_S["AdSelectionServiceImpl"]
MS_S["MeasurementServiceImpl"]
end
subgraph "Data Layer"
TD["TopicsDao<br/>(SQLite)"]
CAD["CustomAudienceDao"]
ASD["AdSelectionDatabase"]
MD["MeasurementDatabase"]
end
subgraph "ML / Classification"
CM_C["ClassifierManager"]
OD_C["OnDeviceClassifier<br/>(TFLite BERT)"]
PC_C["PrecomputedClassifier"]
MM_C["ModelManager"]
end
subgraph "SDK Sandbox"
SSS["SdkSandboxServiceImpl"]
SBP_S["SandboxProcess"]
SSP["SandboxedSdkProvider"]
end
end
TM_F --> TS_S
CAM_F --> CAS_S
ATR_F --> MS_S
SSM_F --> SSS
TS_S --> CM_C
CM_C --> OD_C
CM_C --> PC_C
OD_C --> MM_C
TS_S --> TD
CAS_S --> CAD
ADS_S --> ASD
MS_S --> MD
SSS --> SBP_S
SBP_S --> SSP
Feature Flags:
AdServices uses extensive feature flagging to control rollout:
Key flags control:
- Classifier type (on-device vs precomputed vs both)
- Encryption mode for topic transport
- Background fetch intervals for custom audiences
- SDK sandbox enforcement mode
- Attribution reporting windowing parameters
50.10 Try It¶
Exercise 25-1: Inspect AppFunction Metadata in AppSearch¶
Use the AppSearch shell command to dump indexed app function metadata:
# List all AppSearch databases for a package
adb shell cmd appsearch list-databases --package com.example.app
# Search for AppFunctionStaticMetadata documents
adb shell cmd appsearch query \
--database "appfunctions-static-metadata" \
--query "" \
--schema "AppFunctionStaticMetadata"
Exercise 25-2: AppFunctionManagerService Shell Commands¶
The AppFunctionManagerServiceImpl supports shell commands for testing:
# Check AppFunctions service status
adb shell dumpsys app_function
# List valid agents
adb shell cmd app_function list-agents
# List valid targets for a user
adb shell cmd app_function list-targets --user 0
# Check access state
adb shell cmd app_function get-access-state \
--agent com.example.agent \
--target com.example.target
Exercise 25-3: Implement a Minimal AppFunctionService¶
Create a service that exposes a "createNote" function:
public class NoteAppFunctionService extends AppFunctionService {
@Override
public void onExecuteFunction(
ExecuteAppFunctionRequest request,
String callingPackage,
SigningInfo callingPackageSigningInfo,
CancellationSignal cancellationSignal,
OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> callback) {
String functionId = request.getFunctionIdentifier();
if ("createNote".equals(functionId)) {
GenericDocument params = request.getParameters();
String title = params.getPropertyString("title");
String body = params.getPropertyString("body");
// Create the note in your app's database
long noteId = createNoteInDb(title, body);
// Build response
GenericDocument result = new GenericDocument.Builder<>("", "", "NoteResult")
.setPropertyLong(
ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE, noteId)
.build();
callback.onResult(new ExecuteAppFunctionResponse(result));
} else {
callback.onError(new AppFunctionException(
AppFunctionException.ERROR_FUNCTION_NOT_FOUND,
"Unknown function: " + functionId));
}
}
}
Register in AndroidManifest.xml:
<service android:name=".NoteAppFunctionService"
android:permission="android.permission.BIND_APP_FUNCTION_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.app.appfunctions.AppFunctionService" />
</intent-filter>
</service>
Exercise 25-4: Call an AppFunction¶
AppFunctionManager afm = context.getSystemService(AppFunctionManager.class);
GenericDocument params = new GenericDocument.Builder<>("", "", "CreateNoteParams")
.setPropertyString("title", "Meeting Notes")
.setPropertyString("body", "Discuss Q3 roadmap")
.build();
ExecuteAppFunctionRequest request = new ExecuteAppFunctionRequest.Builder(
"com.example.noteapp", "createNote")
.setParameters(params)
.build();
CancellationSignal cancellation = new CancellationSignal();
afm.executeAppFunction(request, executor, cancellation,
new OutcomeReceiver<>() {
@Override
public void onResult(ExecuteAppFunctionResponse response) {
GenericDocument result = response.getResultDocument();
long noteId = result.getPropertyLong(
ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE);
Log.d(TAG, "Created note with ID: " + noteId);
}
@Override
public void onError(AppFunctionException error) {
Log.e(TAG, "Error: " + error.getErrorCode()
+ " (" + error.getErrorCategory() + ")");
}
});
Exercise 25-5: Computer Control Session¶
Request a computer control session and take a screenshot:
ComputerControlExtensions extensions =
ComputerControlExtensions.getInstance(context);
if (extensions == null) {
Log.w(TAG, "Computer Control not available on this device");
return;
}
ComputerControlSession.Params params = new ComputerControlSession.Params.Builder()
.setName("my-automation-session")
.setTargetPackageNames(List.of("com.example.target"))
.setDisplayWidthPx(1080)
.setDisplayHeightPx(2400)
.setDisplayDpi(420)
.setDisplaySurface(mySurface)
.build();
extensions.requestSession(params, executor,
new ComputerControlSession.Callback() {
@Override
public void onSessionPending(IntentSender intentSender) {
// Show user approval UI
startIntentSenderForResult(intentSender, REQUEST_CODE, ...);
}
@Override
public void onSessionCreated(ComputerControlSession session) {
// Launch an app
session.launchApplication("com.example.target");
// Set up stability listener
session.setStabilityListener(executor, () -> {
// UI is stable, take a screenshot
Image screenshot = session.getScreenshot();
if (screenshot != null) {
// Process the screenshot with your AI model
processScreenshot(screenshot);
screenshot.close();
}
});
}
@Override
public void onSessionCreationFailed(int errorCode) {
Log.e(TAG, "Session creation failed: " + errorCode);
}
@Override
public void onSessionClosed() {
Log.d(TAG, "Session closed");
}
});
Exercise 25-6: Inspect NNAPI Devices¶
# List available NNAPI accelerators
adb shell dumpsys neuralnetworks
# Run the NNAPI sample test
adb shell /data/local/tmp/NeuralNetworksTest_static \
--gtest_filter=*TrivialModel*
Exercise 25-7: OnDeviceIntelligence Shell Commands¶
# Check OnDeviceIntelligence service status
adb shell dumpsys on_device_intelligence
# Query the configured remote service package
adb shell cmd on_device_intelligence get-service-package
# Override the service temporarily (for testing)
adb shell cmd on_device_intelligence set-temporary-service \
--component com.example.test/.TestInferenceService \
--duration 60000
Exercise 25-8: Explore Content Capture¶
# Check Content Capture status
adb shell dumpsys content_capture
# Enable content capture debugging
adb shell settings put secure content_capture_enabled 1
# View captured content for a specific package
adb shell dumpsys content_capture --verbose --package com.example.app
Exercise 25-9: Topics API Debugging¶
# Check AdServices status
adb shell dumpsys adservices
# Force epoch computation (normally weekly)
adb shell device_config put adservices topics_epoch_job_period_ms 60000
# View classified topics
adb shell cmd adservices topics list
Exercise 25-10: Build and Test AppFunctions¶
# Build the AppFunctions framework module
cd $AOSP_ROOT
m AppFunctionManagerService
# Run unit tests
atest AppFunctionManagerServiceImplTest
# Run CTS tests for AppFunctions
atest CtsAppFunctionTestCases
Exercise 25-11: Implement a ComputerControlSession Callback¶
public class AutomationCallback implements ComputerControlSession.Callback {
private ComputerControlSession mSession;
@Override
public void onSessionPending(IntentSender intentSender) {
// In a real app, present this to the user for approval
Log.d(TAG, "Session pending user approval");
try {
startIntentSenderForResult(intentSender, REQUEST_CODE,
null, 0, 0, 0);
} catch (IntentSender.SendIntentException e) {
Log.e(TAG, "Failed to start approval UI", e);
}
}
@Override
public void onSessionCreated(ComputerControlSession session) {
mSession = session;
Log.d(TAG, "Session created with display ID: "
+ session.getParams().getDisplayWidthPx() + "x"
+ session.getParams().getDisplayHeightPx());
// Launch the target app
session.launchApplication("com.example.target");
// Wait for stability before taking action
session.setStabilityListener(Runnable::run, () -> {
Image screenshot = session.getScreenshot();
if (screenshot != null) {
// Analyze with AI model
analyzeAndAct(session, screenshot);
screenshot.close();
}
});
}
private void analyzeAndAct(ComputerControlSession session, Image image) {
// Example: tap the center of the screen
int centerX = image.getWidth() / 2;
int centerY = image.getHeight() / 2;
session.tap(centerX, centerY);
// Example: type text into a field
session.insertText("Hello from AI", /* replaceExisting= */ true,
/* commit= */ false);
// Example: swipe down
session.swipe(centerX, 200, centerX, 800);
}
@Override
public void onSessionCreationFailed(int errorCode) {
switch (errorCode) {
case ComputerControlSession.ERROR_SESSION_LIMIT_REACHED:
Log.w(TAG, "Too many sessions");
break;
case ComputerControlSession.ERROR_DEVICE_LOCKED:
Log.w(TAG, "Device is locked");
break;
case ComputerControlSession.ERROR_PERMISSION_DENIED:
Log.w(TAG, "User denied permission");
break;
}
}
@Override
public void onSessionClosed() {
Log.d(TAG, "Session closed");
mSession = null;
}
}
Exercise 25-12: Query OnDeviceIntelligence Features¶
OnDeviceIntelligenceManager odim =
context.getSystemService(OnDeviceIntelligenceManager.class);
if (odim == null) {
Log.w(TAG, "OnDeviceIntelligence not available");
return;
}
// Check implementation version
odim.getVersion(executor, version -> {
Log.d(TAG, "ODI version: " + version);
});
// List available features
odim.listFeatures(executor, new OutcomeReceiver<>() {
@Override
public void onResult(List<Feature> features) {
for (Feature feature : features) {
Log.d(TAG, "Feature: " + feature.getId()
+ " params: " + feature.getFeatureParams());
// Get feature details
odim.getFeatureDetails(feature, executor, new OutcomeReceiver<>() {
@Override
public void onResult(FeatureDetails details) {
Log.d(TAG, "Feature details: " + details);
}
@Override
public void onError(OnDeviceIntelligenceException e) {
Log.e(TAG, "Failed: " + e.getErrorCode());
}
});
}
}
@Override
public void onError(OnDeviceIntelligenceException e) {
Log.e(TAG, "Failed to list features: " + e.getErrorCode());
}
});
Exercise 25-13: Use AppSearch for Function Discovery¶
AppSearchManager appSearchManager =
context.getSystemService(AppSearchManager.class);
// Create a global search session to find app functions
AppSearchManager.SearchContext searchContext =
new AppSearchManager.SearchContext.Builder()
.setDatabaseName("appfunctions-static-metadata")
.build();
appSearchManager.createSearchSession(searchContext, executor, result -> {
AppSearchSession session = result.getResultValue();
// Search for functions that handle "CreateNote"
SearchSpec searchSpec = new SearchSpec.Builder()
.addFilterSchemas("AppFunctionStaticMetadata")
.setRankingStrategy(SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE)
.build();
SearchResults results = session.search("CreateNote", searchSpec);
results.getNextPage(executor, page -> {
for (SearchResult searchResult : page.getResultValue()) {
GenericDocument doc = searchResult.getGenericDocument();
String functionId = doc.getPropertyString("functionIdentifier");
String packageName = doc.getNamespace();
Log.d(TAG, "Found function: " + functionId
+ " in package: " + packageName);
}
});
});
Exercise 25-14: AppFunction Access Management¶
AppFunctionManager afm = context.getSystemService(AppFunctionManager.class);
// Check access state before execution
String targetPackage = "com.example.noteapp";
int accessState = afm.getAccessRequestState(targetPackage);
switch (accessState) {
case AppFunctionManager.ACCESS_REQUEST_STATE_GRANTED:
Log.d(TAG, "Access granted, can execute functions");
break;
case AppFunctionManager.ACCESS_REQUEST_STATE_DENIED:
Log.d(TAG, "Access denied, request via UI");
// Create and launch access request intent
Intent requestIntent = afm.createRequestAccessIntent(targetPackage);
startActivityForResult(requestIntent, ACCESS_REQUEST_CODE);
break;
case AppFunctionManager.ACCESS_REQUEST_STATE_UNREQUESTABLE:
Log.w(TAG, "Cannot request access (not in allowlist, "
+ "or target has no AppFunctionService)");
break;
}
// Check function enabled state
afm.isAppFunctionEnabled("createNote", targetPackage, executor,
new OutcomeReceiver<>() {
@Override
public void onResult(Boolean isEnabled) {
Log.d(TAG, "Function enabled: " + isEnabled);
}
@Override
public void onError(Exception e) {
Log.e(TAG, "Function not found", e);
}
});
Exercise 25-15: NNAPI Model Building (C API)¶
#include <NeuralNetworks.h>
// Create a model
ANeuralNetworksModel* model;
ANeuralNetworksModel_create(&model);
// Add input operand (1x3x3x1 float tensor)
uint32_t inputDims[] = {1, 3, 3, 1};
ANeuralNetworksOperandType inputType = {
.type = ANEURALNETWORKS_TENSOR_FLOAT32,
.dimensionCount = 4,
.dimensions = inputDims,
.scale = 0.0f,
.zeroPoint = 0
};
ANeuralNetworksModel_addOperand(model, &inputType);
// Add filter operand (1x2x2x1 float tensor)
uint32_t filterDims[] = {1, 2, 2, 1};
ANeuralNetworksOperandType filterType = {
.type = ANEURALNETWORKS_TENSOR_FLOAT32,
.dimensionCount = 4,
.dimensions = filterDims
};
ANeuralNetworksModel_addOperand(model, &filterType);
// Add bias operand
uint32_t biasDims[] = {1};
ANeuralNetworksOperandType biasType = {
.type = ANEURALNETWORKS_TENSOR_FLOAT32,
.dimensionCount = 1,
.dimensions = biasDims
};
ANeuralNetworksModel_addOperand(model, &biasType);
// Add scalar operands for padding, stride, activation
ANeuralNetworksOperandType scalarType = {
.type = ANEURALNETWORKS_INT32
};
for (int i = 0; i < 4; i++) {
ANeuralNetworksModel_addOperand(model, &scalarType);
}
// Add output operand (1x2x2x1 float tensor)
uint32_t outputDims[] = {1, 2, 2, 1};
ANeuralNetworksOperandType outputType = {
.type = ANEURALNETWORKS_TENSOR_FLOAT32,
.dimensionCount = 4,
.dimensions = outputDims
};
ANeuralNetworksModel_addOperand(model, &outputType);
// Add CONV_2D operation
uint32_t inputIndexes[] = {0, 1, 2, 3, 4, 5, 6};
uint32_t outputIndexes[] = {7};
ANeuralNetworksModel_addOperation(model,
ANEURALNETWORKS_CONV_2D,
7, inputIndexes,
1, outputIndexes);
// Mark inputs/outputs and finish
uint32_t modelInputs[] = {0};
uint32_t modelOutputs[] = {7};
ANeuralNetworksModel_identifyInputsAndOutputs(model,
1, modelInputs, 1, modelOutputs);
ANeuralNetworksModel_finish(model);
// Compile
ANeuralNetworksCompilation* compilation;
ANeuralNetworksCompilation_create(model, &compilation);
ANeuralNetworksCompilation_setPreference(compilation,
ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER);
ANeuralNetworksCompilation_finish(compilation);
// Execute
ANeuralNetworksExecution* execution;
ANeuralNetworksExecution_create(compilation, &execution);
// ... set inputs, run, get outputs
// Cleanup
ANeuralNetworksExecution_free(execution);
ANeuralNetworksCompilation_free(compilation);
ANeuralNetworksModel_free(model);
Exercise 25-16: AppFunction Access Flag Management via ADB¶
# Add an agent to the secure setting allowlist
adb shell settings put secure app_function_additional_agent_allowlist \
"com.example.agent"
# Verify the agent is in the allowlist
adb shell cmd app_function list-agents
# Grant access from an agent to a target
adb shell cmd app_function update-access-flags \
--agent com.example.agent \
--target com.example.noteapp \
--set OTHER_GRANTED \
--clear OTHER_DENIED
# Check the current access flags
adb shell cmd app_function get-access-flags \
--agent com.example.agent \
--target com.example.noteapp
# Revoke access
adb shell cmd app_function update-access-flags \
--agent com.example.agent \
--target com.example.noteapp \
--set OTHER_DENIED \
--clear OTHER_GRANTED
# Clear the additional agents setting
adb shell settings delete secure app_function_additional_agent_allowlist
# View access history
adb shell content query \
--uri content://com.android.appfunction.accesshistory/user/0
Exercise 25-17: Implement AppFunction with Attribution¶
// Caller side: include attribution in request
AppFunctionAttribution attribution = new AppFunctionAttribution.Builder()
.setInteractionType(AppFunctionAttribution.INTERACTION_TYPE_USER_QUERY)
.setThreadId("conversation-123")
.setInteractionUri(Uri.parse("myapp://conversation/123"))
.build();
ExecuteAppFunctionRequest request = new ExecuteAppFunctionRequest.Builder(
"com.example.noteapp", "createNote")
.setParameters(params)
.setAttribution(attribution)
.build();
// Target side: read attribution
@Override
public void onExecuteFunction(
ExecuteAppFunctionRequest request,
String callingPackage,
SigningInfo callingPackageSigningInfo,
CancellationSignal cancellationSignal,
OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> callback) {
// Check who is calling
Log.d(TAG, "Called by: " + callingPackage);
// Read attribution if present
AppFunctionAttribution attribution = request.getAttribution();
if (attribution != null) {
Log.d(TAG, "Interaction type: " + attribution.getInteractionType());
Log.d(TAG, "Thread ID: " + attribution.getThreadId());
Log.d(TAG, "Interaction URI: " + attribution.getInteractionUri());
}
// Handle cancellation
cancellationSignal.setOnCancelListener(() -> {
Log.d(TAG, "Request cancelled");
callback.onError(new AppFunctionException(
AppFunctionException.ERROR_CANCELLED,
"User cancelled the request"));
});
// Execute function on background thread
executor.execute(() -> {
try {
GenericDocument result = executeFunction(request);
callback.onResult(new ExecuteAppFunctionResponse(result));
} catch (Exception e) {
callback.onError(new AppFunctionException(
AppFunctionException.ERROR_APP_UNKNOWN_ERROR,
e.getMessage()));
}
});
}
Exercise 25-18: AppFunction with URI Grants¶
// Target side: return a URI grant in the response
@Override
public void onExecuteFunction(
ExecuteAppFunctionRequest request,
String callingPackage,
SigningInfo callingPackageSigningInfo,
CancellationSignal cancellationSignal,
OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> callback) {
// Create the document
Uri documentUri = createDocument(request.getParameters());
// Build response with URI grant
GenericDocument result = new GenericDocument.Builder<>("", "", "DocumentResult")
.setPropertyString(
ExecuteAppFunctionResponse.PROPERTY_RETURN_VALUE,
documentUri.toString())
.build();
// Create URI grant for the caller
AppFunctionUriGrant uriGrant = new AppFunctionUriGrant.Builder(documentUri)
.setModeFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.build();
callback.onResult(new ExecuteAppFunctionResponse(
result, Bundle.EMPTY, List.of(uriGrant)));
}
Exercise 25-19: Computer Control with Mirror Display¶
// Create a session with a mirror for human observation
ComputerControlSession session = ...; // from callback
// Create a mirror display for observation
SurfaceView mirrorView = new SurfaceView(context);
Surface mirrorSurface = mirrorView.getHolder().getSurface();
InteractiveMirror mirror = session.createInteractiveMirror(
720, 1280, mirrorSurface);
// The mirror shows the same content as the automation display
// Human can also inject touch events through the mirror
mirror.sendTouchEvent(new TouchEvent.Builder()
.setX(360)
.setY(640)
.setAction(TouchEvent.ACTION_DOWN)
.build());
// When done, clean up
mirror.close();
session.close();
Exercise 25-20: Debugging Common AppFunction Issues¶
Problem: Function not found
# Check if the target has an AppFunctionService
adb shell dumpsys package com.example.noteapp | grep -A5 "AppFunctionService"
# Check if metadata is indexed
adb shell cmd appsearch query \
--database "appfunctions-static-metadata" \
--query "" \
--namespace "com.example.noteapp"
Problem: Permission denied
# Check if agent has EXECUTE_APP_FUNCTIONS
adb shell dumpsys package com.example.agent | grep EXECUTE_APP_FUNCTIONS
# Check if agent is in allowlist
adb shell cmd app_function list-agents
# Check access state
adb shell cmd app_function get-access-state \
--agent com.example.agent \
--target com.example.noteapp
Problem: Function is disabled
# Check function enabled state in AppSearch
adb shell cmd appsearch query \
--database "appfunctions-runtime-metadata" \
--query "" \
--schema "AppFunctionRuntimeMetadata"
# Re-enable a function
adb shell cmd app_function set-enabled \
--package com.example.noteapp \
--function "createNote" \
--state enabled
Problem: Service binding timeout
# Check if the service is running
adb shell dumpsys activity services | grep AppFunctionService
# Check for ANR issues
adb shell dumpsys activity anr | grep appfunction
# Enable verbose logging
adb shell setprop log.tag.AppFunctionsServiceCall VERBOSE
adb logcat -s AppFunctionsServiceCall
Exercise 25-21: Trace an AppFunction Execution End-to-End¶
Use systrace/perfetto to observe the complete flow:
# Start a perfetto trace capturing binder transactions
adb shell perfetto \
-c - --txt \
-o /data/misc/perfetto-traces/appfunctions.perfetto-trace \
<<EOF
buffers: {
size_kb: 63488
fill_policy: RING_BUFFER
}
data_sources: {
config {
name: "linux.ftrace"
ftrace_config {
ftrace_events: "binder/binder_transaction"
ftrace_events: "binder/binder_transaction_received"
atrace_categories: "am"
atrace_categories: "wm"
}
}
}
duration_ms: 10000
EOF
# Trigger an app function execution during the trace
# Then pull and analyze the trace
adb pull /data/misc/perfetto-traces/appfunctions.perfetto-trace .
50.11 Cross-Subsystem Architecture Patterns¶
50.11.1 The Manager-AIDL-Service Pattern¶
Every AI subsystem in AOSP follows the same three-layer pattern:
graph LR
subgraph "App Process"
MGR["*Manager<br/>(@SystemService)"]
end
subgraph "system_server"
STUB["I*Manager.Stub<br/>(AIDL impl)"]
end
subgraph "Remote Process"
SVC["*Service<br/>(abstract base)"]
end
MGR -- "Binder IPC" --> STUB
STUB -- "bindService" --> SVC
| Component | AppFunctions | Computer Control | ODI | NNAPI | Content Capture |
|---|---|---|---|---|---|
| Manager | AppFunctionManager |
ComputerControlExtensions |
OnDeviceIntelligenceManager |
C API (no Java manager) | ContentCaptureManager |
| AIDL | IAppFunctionManager |
IComputerControlSession |
IOnDeviceIntelligenceManager |
N/A (native) | IContentCaptureManager |
| system_server | AppFunctionManagerServiceImpl |
In VDM service | OnDeviceIntelligenceManagerService |
NeuralNetworksService |
ContentCaptureManagerService |
| Remote Service | AppFunctionService |
Activity on VDisplay | OnDeviceSandboxedInferenceService |
IDevice (HAL) |
ContentCaptureService |
50.11.2 Permission Model Comparison¶
graph TB
subgraph "Runtime Permissions"
P1["EXECUTE_APP_FUNCTIONS<br/>(AppFunctions)"]
P2["ACCESS_COMPUTER_CONTROL<br/>(Computer Control)"]
P3["USE_ON_DEVICE_INTELLIGENCE<br/>(ODI)"]
P4["ACCESS_ADSERVICES_TOPICS<br/>(Topics)"]
end
subgraph "Binding Permissions"
B1["BIND_APP_FUNCTION_SERVICE"]
B2["BIND_TEXTCLASSIFIER_SERVICE"]
B3["BIND_ONDEVICE_SANDBOXED_INFERENCE_SERVICE"]
B4["BIND_CONTENT_CAPTURE_SERVICE"]
end
subgraph "Management Permissions"
M1["MANAGE_APP_FUNCTION_ACCESS"]
end
50.11.3 Data Wire Formats¶
| Subsystem | Wire Format | Serialization |
|---|---|---|
| AppFunctions | GenericDocument (AppSearch) |
Parcelable |
| Computer Control | Image / VirtualTouchEvent |
Raw pixels / Parcelable |
| ODI | Bundle / PersistableBundle |
Parcelable |
| NNAPI | Shared memory buffers | Native (ashmem/ion) |
| Content Capture | ContentCaptureEvent |
Parcelable (batched) |
| AppSearch | GenericDocument |
Parcelable / Icing protobuf |
| Topics | Topic |
Parcelable |
50.11.4 Thread and Executor Patterns¶
Most AI subsystems dispatch work off the Binder thread pool:
graph TD
A[Binder Thread Pool] --> B{Dispatch}
B --> C["THREAD_POOL_EXECUTOR<br/>AppFunctions"]
B --> D["Executors.newCachedThreadPool<br/>ODI"]
B --> E["Background Thread<br/>Content Capture"]
B --> F["Main Executor<br/>AppFunctionService callback"]
AppFunctions uses its own THREAD_POOL_EXECUTOR:
// frameworks/base/services/appfunctions/.../AppFunctionExecutors.java
static final Executor THREAD_POOL_EXECUTOR = ...;
ODI uses multiple cached thread pools for different purposes:
// OnDeviceIntelligenceManagerService.java
private final Executor resourceClosingExecutor = Executors.newCachedThreadPool();
private final Executor callbackExecutor = Executors.newCachedThreadPool();
private final Executor broadcastExecutor = Executors.newCachedThreadPool();
private final Executor mLifecycleExecutor = Executors.newSingleThreadExecutor(
r -> new Thread(r, "odi-lifecycle-broadcast"));
50.11.5 Cancellation Pattern¶
All asynchronous AI APIs support cancellation through the same mechanism:
sequenceDiagram
participant App
participant SystemServer
participant RemoteService
App->>SystemServer: request(... cancelSignal)
SystemServer->>RemoteService: execute(... cancelTransport)
Note over SystemServer: cancelSignal.setRemote(cancelTransport)
App->>App: cancellationSignal.cancel()
App->>SystemServer: ICancellationSignal.cancel()
SystemServer->>RemoteService: CancellationSignal fires
RemoteService->>RemoteService: Stop processing
The ICancellationSignal transport crosses the Binder boundary so that
cancellation in the app process propagates to the remote service.
50.12 Evolution and Future Direction¶
50.12.1 Historical Timeline¶
gantt
title AOSP AI Feature Timeline
dateFormat YYYY
section Core ML
NNAPI (8.1) :2017, 2025
section Intelligence
TextClassifier (8.0) :2017, 2025
Content Capture (10) :2019, 2025
AppPrediction (10) :2019, 2025
section Privacy
AdServices (13) :2022, 2025
OnDevicePersonalization (14) :2023, 2025
section Agents
OnDeviceIntelligence (15) :2024, 2025
AppFunctions (16) :2024, 2025
Computer Control (16) :2025, 2025
The trend is clear: Android is evolving from passive intelligence (capturing and classifying) toward active agent capabilities (executing functions, controlling apps).
50.12.2 The Agent Architecture Stack¶
Looking at all the pieces together, a modern AI agent on Android uses multiple layers:
graph TB
subgraph "Agent Intelligence"
LLM["Large Language Model<br/>(via OnDeviceIntelligence)"]
end
subgraph "Agent Actions"
AF["Structured Actions<br/>(AppFunctions)"]
CC["UI Actions<br/>(Computer Control)"]
end
subgraph "Agent Perception"
AS["Function Discovery<br/>(AppSearch)"]
CCap["Context Understanding<br/>(Content Capture)"]
TC["Text Understanding<br/>(TextClassifier)"]
Screenshot["Visual Understanding<br/>(Computer Control screenshots)"]
end
subgraph "Agent Memory"
AH["Action History<br/>(Access History)"]
AP["Usage Patterns<br/>(AppPrediction)"]
end
LLM --> AF
LLM --> CC
AS --> LLM
CCap --> LLM
TC --> LLM
Screenshot --> LLM
AH --> LLM
AP --> LLM
AppFunctions is the "clean path" -- when apps expose structured functions, the agent can invoke them directly with typed parameters and receive typed responses.
Computer Control is the "universal fallback" -- when an app does not expose AppFunctions, the agent can fall back to UI automation, launching the app on a virtual display and controlling it through tap, swipe, and text injection guided by screenshot analysis.
50.12.3 AppFunctions vs Computer Control: When to Use Each¶
| Criterion | AppFunctions | Computer Control |
|---|---|---|
| App cooperation required | Yes (must implement service) | No |
| Reliability | High (typed contract) | Medium (UI can change) |
| Speed | Fast (direct RPC) | Slow (screenshot + analysis loop) |
| Coverage | Only participating apps | Any app with launcher activity |
| Privacy | Parameters visible to target app | Screenshots visible to agent |
| User visibility | Invisible to user | Can show mirror display |
| Complexity | Low (implement one method) | High (vision model needed) |
| Error handling | Typed error codes | Heuristic (check if UI changed) |
Summary¶
This chapter traced Android's AI infrastructure from high-level SDK APIs through system services to hardware accelerators and isolated processes.
AppFunctions introduced a standardized mechanism for AI agents to invoke
app functionality. The framework uses GenericDocument (from AppSearch) as
its wire format, enforces access through a layered permission/allowlist model,
and maintains a full audit trail of agent-to-app interactions. The architecture
follows the classic Android pattern: client manager, AIDL interface,
system_server implementation, and remote service binding.
Computer Control enables AI agents to interact with arbitrary apps through a virtual display -- launching activities, injecting touch/key events, capturing screenshots, and reading accessibility trees. It builds on VirtualDeviceManager infrastructure and adds stability detection so agents know when to act.
OnDeviceIntelligence provides a dual-service architecture where an OEM intelligence service manages model weights while a sandboxed isolated process performs actual inference. The isolation guarantees that even compromised inference code cannot access the network or filesystem.
NNAPI remains the foundation for hardware-accelerated inference, providing
a C API that partitions models across GPU, DSP, and NPU accelerators through
the IDevice HAL interface.
OnDevicePersonalization implements federated learning with TFLite in an isolated process, keeping training data on-device while producing privacy-preserving aggregate models through differential privacy and secure aggregation.
Content Capture, TextClassifier, and AppPrediction form the passive intelligence layer -- capturing UI state, classifying text entities, and predicting app usage to power smart features across the system.
AppSearch provides the on-device indexing engine that underpins function discovery, content search, and metadata management.
AdServices demonstrates the Privacy Sandbox pattern: on-device ML classifiers, sandboxed SDK runtimes, and auction logic that keeps user data local while still enabling advertising functionality.
The common thread across all these subsystems is Android's commitment to on-device intelligence with process isolation. Every subsystem that touches user data does so within carefully bounded processes, with explicit permission gates, and with the system server mediating all cross-boundary communication.
Key Source Files¶
| File | Path |
|---|---|
| AppFunctionManager | frameworks/base/core/java/android/app/appfunctions/AppFunctionManager.java |
| AppFunctionService | frameworks/base/core/java/android/app/appfunctions/AppFunctionService.java |
| AppFunctionManagerServiceImpl | frameworks/base/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java |
| IAppFunctionManager.aidl | frameworks/base/core/java/android/app/appfunctions/IAppFunctionManager.aidl |
| IAppFunctionService.aidl | frameworks/base/core/java/android/app/appfunctions/IAppFunctionService.aidl |
| ComputerControlSession | frameworks/base/core/java/android/companion/virtual/computercontrol/ComputerControlSession.java |
| ComputerControlExtensions | frameworks/base/libs/computercontrol/src/com/android/extensions/computercontrol/ComputerControlExtensions.java |
| OnDeviceIntelligenceManager | frameworks/base/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java |
| OnDeviceSandboxedInferenceService | frameworks/base/packages/NeuralNetworks/framework/platform/java/android/service/ondeviceintelligence/OnDeviceSandboxedInferenceService.java |
| OnDeviceIntelligenceManagerService | frameworks/base/packages/NeuralNetworks/service/platform/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java |
| NNAPI IDevice | packages/modules/NeuralNetworks/common/types/include/nnapi/IDevice.h |
| NeuralNetworks.cpp | packages/modules/NeuralNetworks/runtime/NeuralNetworks.cpp |
| Manager.cpp (NNAPI) | packages/modules/NeuralNetworks/runtime/Manager.cpp |
| IsolatedTrainingService | packages/modules/OnDevicePersonalization/federatedcompute/src/com/android/federatedcompute/services/training/IsolatedTrainingService.java |
| ContentCaptureManager | frameworks/base/core/java/android/view/contentcapture/ContentCaptureManager.java |
| TextClassifierService | frameworks/base/core/java/android/service/textclassifier/TextClassifierService.java |
| AppPredictionManager | frameworks/base/core/java/android/app/prediction/AppPredictionManager.java |
| AppSearchManager | packages/modules/AppSearch/framework/java/android/app/appsearch/AppSearchManager.java |
| TopicsManager | packages/modules/AdServices/adservices/framework/java/android/adservices/topics/TopicsManager.java |
| ComputerControlSessionParams | frameworks/base/core/java/android/companion/virtual/computercontrol/ComputerControlSessionParams.java |
| InteractiveMirrorDisplay | frameworks/base/core/java/android/companion/virtual/computercontrol/InteractiveMirrorDisplay.java |
| AppFunctionException | frameworks/base/core/java/android/app/appfunctions/AppFunctionException.java |
| AppFunctionAttribution | frameworks/base/core/java/android/app/appfunctions/AppFunctionAttribution.java |
| ExecuteAppFunctionRequest | frameworks/base/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java |
| ExecuteAppFunctionResponse | frameworks/base/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java |
| SafeOneTimeCallback | frameworks/base/core/java/android/app/appfunctions/SafeOneTimeExecuteAppFunctionCallback.java |
| RemoteServiceCallerImpl | frameworks/base/services/appfunctions/java/com/android/server/appfunctions/RemoteServiceCallerImpl.java |
| CallerValidatorImpl | frameworks/base/services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java |
| MetadataSyncAdapter | frameworks/base/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java |
| Extension ComputerControlSession | frameworks/base/libs/computercontrol/src/com/android/extensions/computercontrol/ComputerControlSession.java |
| Extension AutomatedPackageListener | frameworks/base/libs/computercontrol/src/com/android/extensions/computercontrol/AutomatedPackageListener.java |
| GenericDocument | packages/modules/AppSearch/framework/java/external/android/app/appsearch/GenericDocument.java |
| AppSearchImpl | packages/modules/AppSearch/service/java/com/android/server/appsearch/external/localstorage/AppSearchImpl.java |
| ContentCaptureService | frameworks/base/core/java/android/service/contentcapture/ContentCaptureService.java |
| CustomAudienceManager | packages/modules/AdServices/adservices/framework/java/android/adservices/customaudience/CustomAudienceManager.java |
| TopicsWorker | packages/modules/AdServices/adservices/service-core/java/com/android/adservices/service/topics/TopicsWorker.java |
| Manager.h (NNAPI) | packages/modules/NeuralNetworks/runtime/Manager.h |
| IDevice.h (NNAPI HAL) | packages/modules/NeuralNetworks/common/types/include/nnapi/IDevice.h |
| FederatedComputeJobManager | packages/modules/OnDevicePersonalization/federatedcompute/src/com/android/federatedcompute/services/scheduling/ |
Glossary of Key Terms¶
| Term | Definition |
|---|---|
| Agent | An AI-powered app that orchestrates other apps (e.g., an assistant) |
| Target | An app that exposes functionality via AppFunctionService |
| Function Identifier | A unique string identifying an app function within a package |
| GenericDocument | AppSearch's universal document type, used as wire format for AppFunctions |
| Feature | An ML model capability in OnDeviceIntelligence (e.g., text generation) |
| Epoch | A time period in the Topics API (~1 week) during which topic data is collected |
| Custom Audience | A user interest group in FLEDGE/Protected Audiences |
| Trusted Display | A virtual display that allows input injection (Computer Control) |
| Isolated Process | An Android process with no network, storage, or content provider access |
| Feature Level | NNAPI version identifier indicating supported operations |
| Burst Execution | NNAPI mechanism for repeated inference with the same compiled model |
| Stability Signal | Computer Control notification that the UI has settled |
| Access Flags | Bitmask tracking how AppFunction access was granted/denied |
| Allowlist | Device-config list of packages permitted to be AppFunction agents |
| Secure Aggregation | Cryptographic protocol that aggregates updates without revealing individuals |
| Differential Privacy | Mathematical guarantee that individual contributions are obscured by noise |