Chapter 51: 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 ML models -- LLMs and other generative or large inference workloads -- 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.
51.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
51.1.1 Taxonomy of AOSP Intelligence Subsystems¶
| Subsystem | API Level | Module? | Purpose |
|---|---|---|---|
| AppFunctions | 16 (introduced), matured in Android 17 | No (framework) | Typed cross-app function invocation, runtime registration, observation |
| 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) |
51.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_FUNCTIONS(orEXECUTE_APP_FUNCTIONS_SYSTEM) plus a signed agent allowlist served by the platformAllowlistManager(frameworks/base/core/java/android/os/allowlist/AllowlistManager.java). 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.
51.2 AppFunctions Framework¶
The AppFunctions framework, introduced as a beta feature in Android 16, reaches
broad availability in Android 17. It 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.
Android 17 grows the framework well beyond the original static, manifest-only model. The headline additions, each detailed later in this chapter, are:
- Runtime (dynamic) function registration: an app can register an
AppFunctioncallback at runtime from anActivityorServiceinstead of declaring a separateAppFunctionServicecomponent (registerAppFunction, guarded byFLAG_ENABLE_DYNAMIC_APP_FUNCTIONS). - First-class discovery, state, and observation APIs moved onto
AppFunctionManager:searchAppFunctions,getAppFunctionStates,getAppFunctionActivityStates, andobserveAppFunctions. - An access-management framework: per (agent, target) access state and
flags, a user-facing management UI, and a signed agent allowlist served by the
platform
AllowlistManagerrather than aDeviceConfigstring. - New permissions:
DISCOVER_APP_FUNCTIONS(discovery without execution) andEXECUTE_APP_FUNCTIONS_SYSTEM(privileged system agents that bypass the allowlist), alongside the originalEXECUTE_APP_FUNCTIONS.
Source tree overview (Android 17):
frameworks/base/core/java/android/app/appfunctions/
AppFunctionManager.java -- Client-side system service
AppFunctionService.java -- Abstract base for static target apps
AppFunction.java -- Runtime function callback interface
RegisterAppFunctionRequest.java -- Runtime registration request
ExecuteAppFunctionRequest.java -- Request parcelable
ExecuteAppFunctionResponse.java -- Response parcelable
AppFunctionException.java -- Typed error hierarchy
AppFunctionMetadata.java -- Static + runtime function metadata
AppFunctionName.java -- (package, identifier) function name
AppFunctionState.java -- Runtime enabled/visibility state
AppFunctionActivityId.java -- Activity-scoped function key
AppFunctionSearchSpec.java -- Discovery query spec
AppFunctionObserver.java -- Change-observation callback
AppFunctionAccessServiceInterface.java -- LocalService for access checks
IAppFunctionManager.aidl -- System server AIDL
IAppFunctionService.aidl -- Static target app AIDL (oneway)
IAppFunctionExecutor.aidl -- Runtime executor AIDL
IExecuteAppFunctionCallback.aidl -- Async result callback
ICancellationCallback.aidl -- Cancellation transport
...
frameworks/base/services/appfunctions/
java/com/android/server/appfunctions/
AppFunctionManagerService.java -- SystemService wrapper
AppFunctionManagerServiceImpl.java -- IAppFunctionManager.Stub
RemoteServiceCallerImpl.java -- Service binding logic
CallerValidatorImpl.java -- Permission + allowlist enforcement
MetadataSyncAdapter.java -- AppSearch metadata sync
AppFunctionsLoggerWrapper.java -- Statsd interaction logging
allowlist/SystemAppFunctionAllowlistReader.java -- AllowlistManager-backed reader
dynamic/MultiUserDynamicAppFunctionRegistry.java -- Runtime registrations
reader/AppFunctionMetadataReader.java -- Static + dynamic metadata reads
observer/AppFunctionMetadataObserver.java -- AppSearch change observers
...
frameworks/base/services/permission/java/com/android/server/permission/access/appfunction/
AppFunctionAccessService.kt -- Persists (agent, target) access state
51.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)
51.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. In
Android 17 the permission requirement is anyOf the two execution permissions
(an app may also execute its own functions with no permission):
// frameworks/base/core/java/android/app/appfunctions/AppFunctionManager.java
@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_PERMISSION_V2)
@RequiresPermission(
anyOf = {
Manifest.permission.EXECUTE_APP_FUNCTIONS,
Manifest.permission.EXECUTE_APP_FUNCTIONS_SYSTEM
},
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);
}
51.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.
setAppFunctionEnabled applies only to functions backed by a static
AppFunctionService component. Runtime functions registered via
registerAppFunction (51.2.x) are enabled exactly while their registration is
live, so their enabled state is governed by registerAppFunction /
AppFunctionRegistration.unregister rather than this method; calling it for a
runtime-registered function throws IllegalArgumentException.
Android 17 also exposes the full runtime state, not just the enabled bit. The
AppFunctionState parcelable
(frameworks/base/core/java/android/app/appfunctions/AppFunctionState.java)
carries the function's AppFunctionName, isEnabled, and visibility, and is
read in bulk through AppFunctionManager.getAppFunctionStates(...) (see 51.2.x).
51.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. The constants are
defined in AppFunctionManager (ACCESS_FLAG_*):
| Flag | Value | Meaning |
|---|---|---|
ACCESS_FLAG_PREGRANTED |
1 |
System pre-granted the access |
ACCESS_FLAG_UPGRADE_GRANTED |
1 << 1 |
Granted as part of a system upgrade |
ACCESS_FLAG_USER_GRANTED |
1 << 2 |
User explicitly granted via UI |
ACCESS_FLAG_USER_DENIED |
1 << 3 |
User explicitly denied via UI (overrides PREGRANTED) |
ACCESS_FLAG_OTHER_GRANTED |
1 << 4 |
Granted via ADB or another mechanism |
ACCESS_FLAG_OTHER_DENIED |
1 << 5 |
Denied via ADB or self-revoke |
In Android 17 the agent allowlist is no longer a DeviceConfig string. It is
served by the platform AllowlistManager
(frameworks/base/core/java/android/os/allowlist/AllowlistManager.java), which
maps a signed agent package to the set of target packages it may access (a
SignedPackage keyed by package name plus certificate digest, with a wildcard
target for "all targets"). The AppFunctions service reads it through
SystemAppFunctionAllowlistReader
(frameworks/base/services/appfunctions/.../allowlist/SystemAppFunctionAllowlistReader.java),
which caches per-agent results in an LruCache:
// frameworks/base/services/appfunctions/.../allowlist/SystemAppFunctionAllowlistReader.java
public class SystemAppFunctionAllowlistReader implements AppFunctionAllowlistReader {
private final LruCache<SignedPackage, ArraySet<String>> mCache;
private final AllowlistManager mAllowlistManager;
@Override
public CompletableFuture<Boolean> isAllowlisted(
String agentPackage, String targetPackageName, int userId) { ... }
}
CallerValidatorImpl checks both the runtime permission and this allowlist
before any execution proceeds. An agent holding EXECUTE_APP_FUNCTIONS_SYSTEM
is treated as a privileged system agent and skips the allowlist entirely;
agents holding only EXECUTE_APP_FUNCTIONS must be allowlisted for the target.
51.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). The interface grew substantially in Android 17 to carry the discovery, observation, runtime registration, and access-management surface:
// frameworks/base/core/java/android/app/appfunctions/IAppFunctionManager.aidl
interface IAppFunctionManager {
ICancellationSignal executeAppFunction(
in ExecuteAppFunctionAidlRequest request,
in IExecuteAppFunctionCallback callback);
// Discovery and observation
void observeAppFunctions(
in AppFunctionAidlSearchSpec aidlSearchSpec,
in IObserveAppFunctionChangesCallback callback);
void unregisterAppFunctionObserver(
in String callingPackage, in UserHandle userHandle,
in IObserveAppFunctionChangesCallback callback);
void getAppFunctionStates(
in List<AppFunctionName> appFunctionNames,
in String callingPackageName, int targetUserId,
in IGetAppFunctionStatesCallback callback);
void getAppFunctionActivityStates(
in List<AppFunctionActivityId> activityIds,
in String callingPackageName, int targetUserId,
in IGetAppFunctionActivityStatesCallback callback);
// Enabled-state lifecycle
void isAppFunctionEnabled(
in String callingPackage, in String targetPackage,
in String functionIdentifier, in UserHandle userHandle,
in IIsAppFunctionEnabledCallback callback);
void setAppFunctionEnabled(
in String callingPackage, in String functionIdentifier,
in UserHandle userHandle, int enabledState,
in ISetAppFunctionEnabledCallback callback);
// Runtime (dynamic) registration
void registerAppFunctions(in String packageName, in List<String> functionIds,
in IAppFunctionExecutor executor, in IBinder activityToken);
void unregisterAppFunctions(in String packageName, in List<String> functionIds,
in IAppFunctionExecutor executor);
// Access management
int getAccessRequestState(in String agentPackageName, int agentUserId,
in String targetPackageName, int targetUserId);
int getAccessFlags(in String agentPackageName, int agentUserId,
in String targetPackageName, int targetUserId);
boolean updateAccessFlags(in String agentPackageName, int agentUserId,
in String targetPackageName, int targetUserId, int flagMask, int flags);
void revokeSelfAccess(in String targetPackageName);
List<String> getValidAgents(int userId);
List<String> getValidTargets(int targetUserId);
Intent createRequestAccessIntent(in String targetPackageName);
void addOnAccessChangedListener(IOnAppFunctionAccessChangeListener listener, int userId);
void removeOnAccessChangedListener(IOnAppFunctionAccessChangeListener listener, int userId);
}
Note the distinct enabled-state callbacks: IIsAppFunctionEnabledCallback for
the query path and ISetAppFunctionEnabledCallback for the mutation path. The
runtime-registration path passes an IAppFunctionExecutor (the in-process
callback the system invokes for dynamically registered functions) rather than
binding a separate component.
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.
51.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>
51.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 AppInteractionAttribution 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.
51.2.8 Attribution and Interaction Logging¶
Every execution can carry an AppInteractionAttribution describing the
interaction that triggered it. In Android 17 this attribution type was promoted
out of the appfunctions package into android.app so it can be shared with the
broader App Interaction API, and it is gated by FLAG_ENABLE_APP_INTERACTION_API:
// frameworks/base/core/java/android/app/AppInteractionAttribution.java
public static final int INTERACTION_TYPE_OTHER = 0; // custom string required
public static final int INTERACTION_TYPE_USER_QUERY = 1;
public static final int INTERACTION_TYPE_USER_SCHEDULED = 2;
An attribution carries the interaction type, an optional custom-type string
(when the type is INTERACTION_TYPE_OTHER), and an optional interaction Uri
that links back to the originating context. The privacy UI uses it to explain to
the user why a function ran.
Rather than persisting a per-call history database, Android 17 records each
execution to the platform metrics pipeline (statsd). The system server's
AppFunctionsLoggerWrapper
(frameworks/base/services/appfunctions/.../AppFunctionsLoggerWrapper.java)
runs on a shared background executor and emits a structured event for every
success or error, normalizing the public attribution constants and tagging the
function's type:
// frameworks/base/services/appfunctions/.../AppFunctionsLoggerWrapper.java
static final int FUNCTION_TYPE_UNSPECIFIED = 0;
static final int FUNCTION_TYPE_STATIC = 1; // AppFunctionService-backed
static final int FUNCTION_TYPE_DYNAMIC_GLOBAL = 2; // registerAppFunction (Service/global)
static final int FUNCTION_TYPE_DYNAMIC_ACTIVITY = 3;// registerAppFunction (Activity-scoped)
void logAppFunctionSuccess(
ExecuteAppFunctionAidlRequest request,
ExecuteAppFunctionResponse response,
int callingUid,
long executionStartTimeMillis,
@AppFunctionMetadata.AppFunctionType int appFunctionType) { ... }
The logged event captures the calling UID, target, the interaction type derived
from the request's AppInteractionAttribution, the function type (static vs.
dynamic, global vs. activity-scoped), the response code, and the execution
latency measured from after the service bind completed.
51.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;
}
51.2.10 System Server Implementation¶
A thin SystemService,
frameworks/base/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java,
constructs and publishes the binder under Context.APP_FUNCTION_SERVICE and
forwards the user lifecycle. The real logic lives in
AppFunctionManagerServiceImpl, which extends IAppFunctionManager.Stub and
coordinates the collaborators wired up by the SystemService:
// frameworks/base/services/appfunctions/.../AppFunctionManagerServiceImpl.java
public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
private final RemoteServiceCaller<IAppFunctionService> mRemoteServiceCaller;
private final CallerValidator mCallerValidator;
private final AppFunctionsLoggerWrapper mLoggerWrapper;
private final IUriGrantsManager mUriGrantsManager;
private final UriGrantsManagerInternal mUriGrantsManagerInternal;
private final MultiUserDynamicAppFunctionRegistry mDynamicAppFunctionRegistry;
private final AppFunctionMetadataReader mAppFunctionMetadataReader;
private final AppFunctionMetadataObserver mAppFunctionMetadataObserver;
private final VisibilityHelper mVisibilityHelper;
private final ActivityTaskManagerInternal mActivityTaskManagerInternal;
// Access checks delegate to the permission subsystem, when enabled:
private final AppFunctionAccessServiceInterface mAppFunctionAccessService;
...
Key supporting classes:
| Class | Responsibility |
|---|---|
RemoteServiceCallerImpl |
Binds to target AppFunctionService, manages connection lifecycle |
CallerValidatorImpl |
Enforces EXECUTE_APP_FUNCTIONS / EXECUTE_APP_FUNCTIONS_SYSTEM, checks the allowlist |
MetadataSyncAdapter |
Syncs static function metadata to AppSearch on package changes |
AppFunctionPackageMonitor |
Watches for package install/update/remove |
MultiUserDynamicAppFunctionRegistry |
Holds runtime (registerAppFunction) registrations per user |
AppFunctionMetadataReader |
Reads static (AppSearch) and dynamic metadata for discovery/state |
AppFunctionMetadataObserver |
Drives observeAppFunctions from AppSearch change observers |
SystemAppFunctionAllowlistReader |
Resolves the signed agent allowlist via AllowlistManager |
AppFunctionsLoggerWrapper |
Emits statsd interaction events for each execution |
AppFunctionAccessService (permission subsystem) |
Persists per (agent, target) access state and flags |
51.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
51.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.
51.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.
51.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.
51.2.15 Multi-User Support¶
The service implementation is multi-user aware. Each user has:
- Their own AppSearch database for static function metadata
- Their own
PackageMonitorfor tracking package changes - Their own slice of the
MultiUserDynamicAppFunctionRegistryfor runtime registrations - Separate access state and flags per (agent, target) pair, persisted by the permission subsystem
// AppFunctionManagerServiceImpl.java
public void onUserUnlocked(TargetUser user) {
if (enableDynamicAppFunctions()) {
mAppFunctionMetadataObserver.registerAppSearchObserverForUser(user);
} else {
registerAppSearchObserver(user);
}
trySyncRuntimeMetadata(user.getUserHandle(), ...);
PackageMonitor pkgMonitorForUser =
AppFunctionPackageMonitor.registerPackageMonitorForUser(
mContext, user, mAppFunctionMetadataObserver);
mPackageMonitors.append(user.getUserIdentifier(), pkgMonitorForUser);
mDynamicAppFunctionRegistry.onUserUnlocked(user, ...);
}
public void onUserStopping(@NonNull TargetUser user) {
if (enableDynamicAppFunctions()) {
mAppFunctionMetadataObserver.unregisterAppSearchObserverForUser(user);
} else {
MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
}
mPackageMonitors.get(user.getUserIdentifier()).unregister();
mPackageMonitors.delete(user.getUserIdentifier());
}
public void onUserStopped(@NonNull TargetUser user) {
mDynamicAppFunctionRegistry.onUserStopped(user);
}
When the dynamic-functions flag is on, the per-user AppSearch observer is owned
by AppFunctionMetadataObserver (which fans changes out both to internal
metadata caches and to client observeAppFunctions callbacks). The runtime
registry is keyed by user so that registrations made by one user's processes are
torn down when that user stops.
51.2.16 Agent Allowlist Architecture¶
In Android 17 the agent allowlist is no longer a merge of DeviceConfig and
Settings.Secure strings. It is served by the platform AllowlistManager
(allowlist id ALLOWLIST_ID_APP_FUNCTION), which returns, for a signed agent
package, the set of target packages it may access. The AppFunctions service
consumes it through SystemAppFunctionAllowlistReader:
graph TD
A["AllowlistManager<br/>(ALLOWLIST_ID_APP_FUNCTION)"] --> B["SystemAppFunctionAllowlistReader<br/>(LruCache + change listener)"]
B --> C["CallerValidatorImpl.isAllowlisted(agent, target, user)"]
C --> E{"Agent allowed<br/>for target?"}
E -->|"Self-call or wildcard or listed target"| F[Allowed]
E -->|Not listed| G[ACCESS_REQUEST_STATE_UNREQUESTABLE]
The reader hashes the agent's latest signing certificate into a SignedPackage
and asks AllowlistManager for that agent's valid targets, caching the result
in an LruCache so repeated executions by the same agent skip the IPC:
// frameworks/base/services/appfunctions/.../allowlist/SystemAppFunctionAllowlistReader.java
@Override
public CompletableFuture<Boolean> isAllowlisted(
String agentPackageName, String targetPackageName, int userId) {
if (agentPackageName.equals(targetPackageName)) {
return AndroidFuture.completedFuture(true); // own functions always allowed
}
SignedPackage agentSignedPackage =
new SignedPackage(agentPackageName, /* certificate digest */ ...);
maybeStartAllowlistListener();
return getValidTargetPackages(agentSignedPackage)
.thenApply(allowlistTargets ->
allowlistTargets.contains(WILDCARD_PACKAGE_NAME)
|| allowlistTargets.contains(targetPackageName));
}
Three behaviors are worth noting:
- Self-access (
agent == target) is implicitly allowed, so an app can always invoke its own functions. - Wildcard targets: an agent allowlisted with the wildcard package may access any target.
- Change listening: on first use the reader registers an
OnAllowlistChangedListener(request idALLOWLIST_ID_APP_FUNCTION) so cache entries are invalidated when the platform allowlist updates, rather than being reloaded from a config string at boot.
51.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 to the agent. The grant is issued through
mUriGrantsManager.grantUriPermissionFromOwner(mPermissionOwner, ...), tying it
to the AppFunctions permission owner so the system can revoke it later; the
grants live until the owner releases them or the device reboots.
51.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.
51.2.19 Service Startup and Lifecycle¶
The framework is a SystemService. AppFunctionManagerService.onStart()
publishes the binder under Context.APP_FUNCTION_SERVICE (only when
AppFunctionManagerConfiguration.isSupported(context) is true) and optionally
publishes the AppInteractionService local service when the App Interaction
API flag is on:
// frameworks/base/services/appfunctions/.../AppFunctionManagerService.java
@Override
public void onStart() {
if (AppFunctionManagerConfiguration.isSupported(getContext())) {
publishBinderService(Context.APP_FUNCTION_SERVICE, mServiceImpl);
}
if (Flags.enableAppInteractionApi()) {
publishLocalService(AppInteractionService.class, mAppInteractionService);
}
}
Per-user state is set up and torn down through onUserStarting,
onUserUnlocked, onUserStopping, and onUserStopped (51.2.15). The agent
allowlist is no longer primed at a boot phase; it is fetched lazily from
AllowlistManager on first use and kept fresh by a change listener (51.2.16).
51.3 Computer Control¶
Computer Control, which debuted (flag-gated) in Android 16 and was substantially
expanded and reworked in Android 17, is the framework that lets AI agents
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. Because it is built on top of VirtualDeviceManager, Computer
Control's virtual-display, input, and lifecycle machinery is covered in depth in
Chapter 52 (CompanionDeviceManager and Virtual Devices); this section focuses on
the agent-facing session API and how it complements AppFunctions.
Source tree (Android 17):
frameworks/base/core/java/android/companion/virtual/computercontrol/
ComputerControlSession.java -- Core session API
ComputerControlSessionParams.java -- Session configuration
InteractiveMirror.java -- Mirror display for user view
ComputerControlConsentManager.java -- Per-session consent flow
ComputerControlAccessibilityProxy.java -- Accessibility tree access
LifecycleState.java / LifecycleStateTracker.java -- Session lifecycle states
AutomatedPackageListener.java -- Package change notifications
IComputerControlSession.aidl -- Session Binder interface
IComputerControlSessionCallback.aidl -- Creation lifecycle callback
IComputerControlLifecycleCallback.aidl -- Lifecycle-state callback
IComputerControlConsentManager.aidl -- Consent Binder interface
IInteractiveMirror.aidl -- Mirror display interface
IAutomatedPackageListener.aidl -- Package listener interface
frameworks/base/libs/computercontrol/ -- Extension library (sidecar APIs)
src/com/android/extensions/computercontrol/
ComputerControlExtensions.java -- Entry point
ComputerControlSession.java -- Extension session wrapper
AutomatedPackageListener.java -- Extension listener
view/MirrorView.java -- Mirror display view
51.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]
VDP[Virtual D-pad]
VTS[Virtual Touchscreen]
end
subgraph "Target App"
ACTIVITY["Activity on<br/>Virtual Display"]
end
CCE -- "requestSession()" --> VDM
VDM -- "creates" --> VD
VDM -- "creates" --> VDP
VDM -- "creates" --> VTS
VDM -- "callback" --> CCS_EXT
CCS_EXT -- "tap/swipe/text" --> CCS_SVC
CCS_SVC -- "inject touch" --> VTS
CCS_SVC -- "inject keys" --> VDP
VD -- "render" --> ACTIVITY
CCS_EXT -- "getScreenshot()" --> VD
AP -- "accessibility tree" --> ACTIVITY
51.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;
51.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);
public void launchApplication(@NonNull ComponentName component);
// Hand over to user
public void handOverApplications();
// Screenshot
@Nullable public Image getScreenshot();
// Gesture-level input (no public low-level send*Event in Android 17)
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);
// Mirror display
@Nullable public InteractiveMirror createInteractiveMirror(
IResultReceiver a11yEmbeddedConnectionReceiver);
// UI stability
public void setStabilityListener(
@NonNull Duration duration, @NonNull Executor executor,
@NonNull StabilityListener listener);
public void clearStabilityListener();
Note that in Android 17 the platform session no longer exposes public
low-level sendKeyEvent / sendTouchEvent methods: agents drive the UI through
the gesture API (tap, swipe, longPress, performAction) and insertText,
and the session translates these to the underlying VirtualTouchscreen /
VirtualDpad.
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();
}
}
51.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.
51.3.5 Interactive Mirror¶
The InteractiveMirror (created via ComputerControlSession.createInteractiveMirror)
mirrors the session's virtual display onto a caller-supplied Surface and
allows a human user to observe and interact simultaneously:
// frameworks/base/core/java/android/companion/virtual/computercontrol/InteractiveMirror.java
public final class InteractiveMirror implements AutoCloseable {
public void setInteractive(boolean interactive);
public void resize(int width, int height);
public void updateInsets(@Nullable Insets insets);
public void close();
}
This enables a "co-pilot" pattern where an AI agent drives the automation
while a human watches and, when setInteractive(true), can intervene.
51.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();
default void onSessionUnstable(@UnstableReason int reason) {}
}
The platform ComputerControlAccessibilityProxy (51.3.27) watches accessibility
events and a first-frame signal to decide when the display content has settled,
then invokes the registered StabilityListener.
51.3.7 Accessibility Integration¶
The platform ComputerControlSession owns a
ComputerControlAccessibilityProxy, which extends AccessibilityDisplayProxy
and is registered for the session's virtual display, letting the agent query
the accessibility tree:
// frameworks/base/core/java/android/companion/virtual/computercontrol/ComputerControlAccessibilityProxy.java
final class ComputerControlAccessibilityProxy extends AccessibilityDisplayProxy {
// registered for the session's virtual display
}
This gives the agent structured information about the UI (view hierarchy, content descriptions, bounding boxes) without relying solely on pixel-level screenshot analysis, and it doubles as the source of the stability signal (51.3.27).
51.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.
51.3.9 Integration with VirtualDeviceManager¶
Computer Control builds on top of the VirtualDeviceManager framework (Chapter 52). 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.
51.3.10 Extension-Layer Action API¶
In Android 17 the extension library exposes a high-level, gesture-oriented API
rather than low-level event wrappers. The earlier TouchEvent / KeyEvent
wrapper classes and a sendTouchEvent path are gone; an agent works in screen
coordinates and lets the extension translate to platform input:
// frameworks/base/libs/computercontrol/.../ComputerControlSession.java
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 performAction(@Action int actionCode); // e.g. BACK, HOME, RECENTS
Each call forwards to the platform ComputerControlSession, which routes the
gesture to the session's VirtualTouchscreen or VirtualDpad and resets the
stability state so the agent's StabilityListener can detect when the UI has
re-settled (51.3.27).
51.3.11 Text Insertion API¶
For text fields, the extension session provides a high-level insertText()
that avoids the complexity of individual key events:
// frameworks/base/libs/computercontrol/.../ComputerControlSession.java
public void insertText(@NonNull String text, boolean replaceExisting, boolean commit);
On the server side this prefers the InputConnection path (51.3.29) to
manipulate the focused text field directly. The commit parameter triggers an
IME action (like pressing "Done" or "Send"); replaceExisting clears the
field's current contents before inserting.
51.3.12 Screenshots and the Perceive-Act Loop¶
Beyond input, the extension session exposes getScreenshot(), returning an
Image captured from the trusted virtual display:
// frameworks/base/libs/computercontrol/.../ComputerControlSession.java
public Image getScreenshot();
Together with the accessibility tree (51.3.7) and the stability signal
(51.3.27), this completes the perceive-act loop: an agent screenshots, reasons
about the pixels (and/or the accessibility nodes), acts via tap/swipe/
insertText, waits for onSessionStable(), then screenshots again.
51.3.13 Interactive Mirror and Co-Pilot Pattern¶
In Android 17 the extension-layer ComputerControlSession returns the platform
InteractiveMirror directly rather than wrapping it in a separate type, and the
extension library ships a MirrorView (a FrameLayout) that hosts the mirror
inside an agent's own UI:
// frameworks/base/libs/computercontrol/.../ComputerControlSession.java
public InteractiveMirror createInteractiveMirror(
AccessibilityEmbeddedConnectionReceiver a11yEmbeddedConnectionReceiver) {
return mSession.createInteractiveMirror(a11yEmbeddedConnectionReceiver);
}
The MirrorView (frameworks/base/libs/computercontrol/.../view/MirrorView.java)
may only be attached to secure, trusted displays.
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
51.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.
51.3.15 Stability Detection Architecture¶
In Android 17 stability detection lives in the platform
ComputerControlAccessibilityProxy, driven by a StabilitySignalTracker. The
flow is:
graph TB
A[Tap injected] --> B["resetStabilityState(reason)"]
C[Key event injected] --> B
D[App launch] --> B
E[Caller interaction] --> B
B --> T["StabilitySignalTracker (timeout = caller Duration)"]
F[Accessibility events] --> T
G[First-frame signal] --> T
T --> J{Quiet for the timeout?}
J -->|Yes| K["onSessionStable"]
J -->|No| L["onSessionUnstable(reason)"]
The agent registers a StabilityListener with a chosen timeout Duration; the
tracker fires onSessionStable() once accessibility events and first-frame
signals stay quiet for that long, and reports onSessionUnstable(reason) while
the screen is still churning (51.3.27).
51.3.16 Extension Library File Inventory (Android 17)¶
The extension library was slimmed down in Android 17; the low-level input wrappers and the separate idle/stability trackers were removed in favor of the platform stability proxy and a gesture-level API:
| File | Purpose |
|---|---|
ComputerControlExtensions.java |
Entry point, session factory, automated-package listener registration |
ComputerControlSession.java |
Extension session wrapper (gesture API, screenshots, accessibility, stability) |
AutomatedPackageListener.java |
Package automation notifications |
view/MirrorView.java |
Mirror display view widget (secure/trusted displays only) |
51.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
51.3.18 System-Server Implementation Overview¶
The extension library described in subsections 51.3.1–51.3.16 is the agent-side API: the client an agent app links and calls. The system-server side of Computer Control lives in a sibling package inside the VirtualDeviceManager (VDM) service tree and contains the actual session state, the policy gates, the binder objects the extension stubs talk to, and the integration with the input, display, and accessibility stacks.
Source tree:
frameworks/base/services/companion/java/com/android/server/companion/virtual/computercontrol/
ComputerControlSessionProcessor.java -- Session creation and policy gate
ComputerControlSessionImpl.java -- Core session binder
ComputerControlAllowlistController.java -- Per-session package allowlist policy
InteractiveMirrorImpl.java -- Mirror display + virtual touchscreen
AutomatedPackagesRepository.java -- Tracks automated packages for launchers
ComputerControlDataStore.java -- Persisted session/consent state
SessionLifecycle.java -- Session lifecycle state machine
ComputerControlStatsController.java -- Metrics
In Android 17 UI-stability detection moved out of a dedicated server-side
calculator and into the agent-side ComputerControlAccessibilityProxy, which
tracks accessibility events and fires the session's StabilityListener
(51.3.6). The diagram below shows how the extension-side session relates to its
system-server counterparts.
graph LR
subgraph Agent["Agent app process"]
EXT["ComputerControlSession (extension)"]
end
subgraph SS["system_server: VirtualDeviceManagerService"]
SP["ComputerControlSessionProcessor"]
IMPL["ComputerControlSessionImpl"]
ALLOW["ComputerControlAllowlistController"]
MIRROR["InteractiveMirrorImpl"]
APR["AutomatedPackagesRepository"]
end
subgraph VDM["VirtualDeviceManager primitives"]
VD["VirtualDevice + trusted display<br/>+ virtual inputs"]
end
EXT -- "requestSession" --> SP
SP -- "creates" --> IMPL
IMPL -- "owns" --> VD
IMPL -- "enforces" --> ALLOW
IMPL -- "owns" --> MIRROR
IMPL -- "registers" --> APR
The package sits inside services/companion/, not inside services/core/,
because Computer Control is part of the VDM subsystem. Chapter 52 walks the
general VDM machinery — VirtualDevice creation, virtual display surfaces,
virtual input dispatch — that Computer Control composes on top of.
51.3.19 ComputerControlSessionProcessor: Session Creation and Limits¶
ComputerControlSessionProcessor owns the entry-point logic for creating
sessions. The policy flow runs in this order — note that AppOps short-
circuits the rest when it returns MODE_ALLOWED:
- AppOps consent check. The processor calls
noteOpNoThrow(OP_COMPUTER_CONTROL, request.attributionSource(), ...)(frameworks/base/services/companion/java/com/android/server/companion/virtual/computercontrol/ComputerControlSessionProcessor.java). If the result isMODE_ALLOWED— meaning the user previously chose "Always Allow" for this agent package — the processor proceeds directly to session creation, bypassing the precondition checks and the consent dialog. Any other mode means consent is required, and the flow continues. - Device-locked gate. The processor checks the keyguard first. If the
device is locked, it rejects with
ERROR_DEVICE_LOCKEDand the flow ends. - Concurrent-session cap. The constant
MAXIMUM_CONCURRENT_SESSIONS(ComputerControlSessionProcessor.java, currently1in Android 17) bounds how many Computer Control sessions can be live system-wide at once. Exceeding it returnsERROR_SESSION_LIMIT_REACHED. - Consent dialog. If preconditions pass and consent is required, the
processor launches
RequestComputerControlAccessActivityvia anIntentSenderreturned to the agent.
The class header documents the role explicitly: "This class enforces session
creation policies, such as limiting the number of concurrent..."
(ComputerControlSessionProcessor.java).
Once the policy flow completes successfully, the processor allocates the
underlying VirtualDevice,
the trusted VirtualDisplay, and the session's virtual input devices, then
constructs a ComputerControlSessionImpl and hands its binder back to the
caller through the original ComputerControlSession.Callback.
The session limit is global, not per-agent. In Android 17 it is 1, so the
framework admits a single Computer Control session at a time. The limit is a
defensive bound, not a tuning knob — hitting it indicates either an agent-side
leak (failure to close sessions) or a second agent racing for control that the
system declines to admit without a deliberate policy change.
51.3.20 ComputerControlSessionImpl: The Session Binder¶
ComputerControlSessionImpl is the actual binder object that backs
IComputerControlSession.aidl
(frameworks/base/services/companion/java/com/android/server/companion/virtual/computercontrol/ComputerControlSessionImpl.java).
It is the largest single file in the Computer Control system-server package,
but its size is dominated by input routing, parameter validation, and
lifecycle teardown — not by business logic.
Its responsibilities, ordered by lifecycle:
- Construction. Receives the trusted
VirtualDevice, theVirtualDisplay, the session's virtual input devices, the calling agent'sAttributionSource, and the requestedtargetPackageNamesallowlist from the processor. - Input dispatch. Implements
tap,swipe,longPress,insertText, andperformActionby routing to the appropriate virtual input device or to the IME-integration path (51.3.29). - Screenshot. Implements
getScreenshot()via the trusted display's surface-capture path; the trusted flag is what makes capture permissible without holdingREAD_FRAME_BUFFER. - Application launch. Implements
launchApplication(packageName)after checking the package against the session's allowlist; an automated launch surfacesAutomatedAppLaunchWarningActivity(51.3.24). - Stability. Every input dispatch and app launch resets the session's
stability state; the stability signal itself is computed agent-side by
ComputerControlAccessibilityProxy(51.3.27). - Mirror display. Optionally owns an
InteractiveMirrorImplwhen the session requested a live view (51.3.5). - Lifecycle teardown. Calls
Binder.linkToDeath()on the agent's callback so an agent process crash auto-closes the session, releases the VirtualDevice, and clears the session's row in theAutomatedPackagesRepository.
The binder-on-binder structure — agent holds an IComputerControlSession
stub, system server holds an IComputerControlSessionCallback stub — is the
standard AOSP pattern; the death-link runs both ways so neither side can
hold the other's resources after a process exit.
51.3.21 Virtual Input Devices: Product IDs and Trusted Display¶
In Android 17 a Computer Control session owns two virtual input devices, each constructed with a fixed product ID in a Computer-Control-reserved product-ID range so the input system can distinguish them from physical inputs and from other virtual-display sessions:
| Device | Product ID | Constant | Purpose |
|---|---|---|---|
| Virtual D-pad | 0xCC01 |
PRODUCT_ID_DPAD |
Key events (directional and character keys) |
| Virtual touchscreen | 0xCC03 |
PRODUCT_ID_TOUCHSCREEN |
Tap, swipe, long-press gestures |
The constants are declared in ComputerControlSessionImpl (with a fixed
VENDOR_ID of 0x0000). Note there is no separate virtual keyboard device:
key events flow through the VirtualDpad (sendKeyEvent), and rich text entry
routes through the IME integration path (51.3.29). The 0xCC prefix carves out
a Computer-Control-reserved block inside the broader VDM virtual-input
product-ID space; see Chapter 52 for the generic VirtualInputDevice scheme
that hosts Computer Control's inputs.
The session's display is a trusted VirtualDisplay. The trust flag has
three observable consequences that distinguish it from a stock virtual
display:
- Animations disabled. System and app animations are suppressed on this display so the agent's per-action stability detection does not have to wait for animation completion before reading the next state.
- IME hidden. Soft keyboards do not auto-show on the display; text input
either uses
VirtualDpadkey events or routes through the IME integration path in 51.3.29. - Focus-stealing disabled. Child windows on the display cannot steal focus from the agent's target activity, so a pop-up cannot redirect the agent's subsequent inputs to an unrelated surface.
Together these turn the display into a deterministic surface the agent can drive without the user's UX-pleasantness layer adding noise.
51.3.22 Session Creation Flow End-to-End¶
This is what happens from the agent's requestSession() call to a
ready-to-drive session.
sequenceDiagram
participant Agent as Agent app
participant Ext as ComputerControlExtensions
participant VDMS as VirtualDeviceManagerService
participant SP as ComputerControlSessionProcessor
participant Consent as RequestComputerControlAccessActivity
participant Impl as ComputerControlSessionImpl
participant VD as VirtualDevice + display + inputs
Agent->>Ext: requestSession(params, callback)
Ext->>VDMS: requestComputerControlSession
VDMS->>SP: process(params, attributionSource)
SP->>SP: noteOpNoThrow OP_COMPUTER_CONTROL
alt mode != MODE_ALLOWED
SP->>SP: checkPreconditions: keyguard
SP->>SP: checkPreconditions: MAXIMUM_CONCURRENT_SESSIONS
SP->>Consent: launch via IntentSender
Consent-->>SP: Allow / Don't Allow / Always
end
SP->>VD: create trusted display + virtual inputs
SP->>Impl: new ComputerControlSessionImpl
Impl-->>Ext: onSessionCreated(binder)
Ext-->>Agent: callback.onSessionCreated(session)
The consent step is conditional: an agent that has been granted Always Allow in a prior session skips the dialog because the AppOps record carries that decision forward. The AppOps record is per-package and per-user, so revoking via Settings sends the next request back through the dialog path without the agent noticing on the request itself — the agent simply observes the dialog appear or not.
Once onSessionCreated fires, the agent owns a binder it can call repeatedly
without round-tripping through the processor. Each input call goes
agent → ComputerControlSessionImpl directly; the processor is only
consulted at session creation.
51.3.23 Error Codes and Session Constraints¶
Session creation returns one of three error codes when it fails. The
constants live in the public extension API at
frameworks/base/core/java/android/companion/virtual/computercontrol/ComputerControlSession.java:
| Code | Value | Condition |
|---|---|---|
ERROR_SESSION_LIMIT_REACHED |
1 | Too many active Computer Control sessions system-wide |
ERROR_DEVICE_LOCKED |
2 | Keyguard is up at session-creation time |
ERROR_PERMISSION_DENIED |
3 | Per-session consent denied by the user |
These are defined alongside ERROR_UNKNOWN (0) in ComputerControlSession.java.
The three errors map cleanly to the three policy checks in
ComputerControlSessionProcessor (51.3.19): one error per policy. An agent
implementing robust retry logic distinguishes them by behavior:
ERROR_SESSION_LIMIT_REACHEDis transient — wait and retry.ERROR_DEVICE_LOCKEDis user-blocked — prompt the user to unlock; retry on screen-on.ERROR_PERMISSION_DENIEDis durable for the request — escalate to the user through the agent's own UX before requesting again, and consider that re-requesting too aggressively will surface to the user as harassment.
The device-locked gate is checked at creation, not maintained for the session lifetime. An agent with a session open before the screen locks keeps the session; the agent simply cannot perform actions on the locked screen until unlock. Tearing the session down on lock would race with foreground-app behavior and break agents that legitimately span a brief screen-off.
51.3.24 The Consent Activity Flow¶
The per-session consent dialog is RequestComputerControlAccessActivity
(frameworks/base/packages/VirtualDeviceManager/src/com/android/virtualdevicemanager/RequestComputerControlAccessActivity.java).
It is a platform-signed activity inside the VDM platform package that the
agent cannot launch directly; it is launched only via the IntentSender
returned by the processor when consent is missing.
The dialog presents three choices:
- Allow — grant consent for the duration of this session only. AppOps records the grant scoped to the session.
- Don't Allow — deny. Processor returns
ERROR_PERMISSION_DENIEDto the caller. - Always Allow — record a persistent AppOps grant for the agent package.
Future
requestSession()calls from the same package skip the dialog entirely.
The activity carries android:filterTouchesWhenObscured="true" — the same
anti-tapjacking flag used by RequestPermissionActivity — so that an overlay
window cannot pass touches through to the consent buttons. This matters
because a Computer Control consent grant is particularly attractive to a
tapjacking adversary: a successful grant gives the adversary's agent the
ability to drive the user's other apps from inside a sanctioned session.
When an agent automates an app launch mid-session — for example, a launchApplication()
call that drives the user into a different app — the system
surfaces AutomatedAppLaunchWarningActivity
(frameworks/base/packages/VirtualDeviceManager/src/com/android/virtualdevicemanager/AutomatedAppLaunchWarningActivity.java)
to make the automated launch visible to the user rather than letting it happen
silently. A silent launch would leave the user wondering why an app appeared
on its own; the warning tells the user which package the agent is opening and
on whose behalf before the launch proceeds.
51.3.25 AppOps and Per-Session Tracking¶
Computer Control uses the AppOps system to track per-package consent state.
The relevant op is OP_COMPUTER_CONTROL in
frameworks/base/core/java/android/app/AppOpsManager.java, defined alongside
its op string OPSTR_COMPUTER_CONTROL and tied to the
ACCESS_COMPUTER_CONTROL permission:
AppOps records grants with a mode (MODE_ALLOWED, MODE_IGNORED,
MODE_ERRORED, MODE_DEFAULT) scoped by package + attribution. Each
session creation calls
noteOpNoThrow(OP_COMPUTER_CONTROL, request.attributionSource(), ...)
in ComputerControlSessionProcessor. The result determines the
next step: MODE_ALLOWED short-circuits straight to session creation
(skipping both preconditions and the consent dialog); any other mode means
the processor advances to the keyguard and concurrent-cap precondition
checks, and on success launches the consent activity. The no-throw variant
returns the mode as an int instead of throwing SecurityException, which
is the right shape for a router that branches on the result rather than
bailing out.
This is the same machinery used for sensitive ops like OP_CAMERA,
OP_RECORD_AUDIO, and OP_FINE_LOCATION. Treating Computer Control as an
AppOp rather than a one-shot permission has two implications worth pulling
out:
- Revocability. A user can revoke Computer Control for a specific agent
in Settings without uninstalling the agent. Subsequent
requestSession()calls from that agent route back through the dialog. - Auditability. AppOps records every
noteOpinvocation, so a privacy dashboard can show which agents have requested Computer Control and when, even after the sessions have ended.
The matching permission android.permission.ACCESS_COMPUTER_CONTROL
(frameworks/base/core/res/AndroidManifest.xml) has protection level
internal|privileged and is itself gated by the
android.companion.virtualdevice.flags.computer_control_access feature flag,
meaning only privileged (preinstalled) agents can request a Computer Control
session in the first place. The AppOps layer adds the per-grant user-facing
control on top of that platform-level gate; the two together implement defense
in depth: a non-privileged third-party app cannot even ask, and a privileged
agent cannot grant itself.
51.3.26 Anti-Tampering Mechanisms¶
The threat model around Computer Control assumes a malicious app could attempt to (a) trick a user into granting Computer Control consent, (b) hijack an already-granted session, or (c) ride a granted session into apps the user did not intend to expose. The framework defends each with a distinct mechanism:
- FilterTouches on consent activities. Both
RequestComputerControlAccessActivityandAutomatedAppLaunchWarningActivityare declared with a FilterTouches theme (Theme.RequestComputerControlAccessActivity.FilterTouches/Theme.AutomatedAppLaunchWarningActivity.FilterTouchesin the VDMres/values/themes.xml, each settingandroid:filterTouchesWhenObscured), so an overlay window cannot pass touches through to the consent buttons. This blocks the classic tapjacking attack against permission dialogs — the same pattern that surfaced throughSYSTEM_ALERT_WINDOWabuse in earlier Android releases. - Per-session package allowlist.
ComputerControlAllowlistControllerrejects an agent'slaunchApplication(packageName)unlesspackageNamewas declared intargetPackageNamesat session creation. A Computer Control session that opened a messaging app cannot subsequently launch a banking app inside the same session. - Visible automated-launch notice. When the agent automates an app launch,
the system surfaces
AutomatedAppLaunchWarningActivity(51.3.24) rather than opening the app silently, making the agent's launch visible at the moment it would otherwise be invisible to the user. - Binder death monitoring.
ComputerControlSessionImplcallsBinder.linkToDeath()on the agent's callback binder. If the agent process is killed — by oom-killer, by the user swiping it from Recents, by a crash — the system auto-closes the session and releases theVirtualDevice. This prevents a long-lived orphan session from continuing to drive the device after its operator has gone away.
The mechanisms compose. An attacker who somehow bypassed tapjacking protection on the consent activity (mechanism 1) and obtained a session would still be blocked by the activity allowlist (mechanism 2) from expanding the session's reach; an attacker who got past both (mechanism 3) would still surface a launch warning to the user; an attacker whose implant process died would release the session immediately (mechanism 4).
51.3.27 Stability Detection via the Accessibility Proxy¶
In Android 17 stability detection is computed agent-side, not by a dedicated
server-side calculator. ComputerControlAccessibilityProxy
(frameworks/base/core/java/android/companion/virtual/computercontrol/ComputerControlAccessibilityProxy.java)
owns a StabilitySignalTracker that decides when the UI has settled after an
injected event and invokes the session's StabilityListener.
The decision is timeout-based. The agent registers a listener with a caller
chosen Duration:
// frameworks/base/core/java/android/companion/virtual/computercontrol/ComputerControlSession.java
public interface StabilityListener {
void onSessionStable();
default void onSessionUnstable(@UnstableReason int reason) {}
}
public void setStabilityListener(
@NonNull Duration duration, @NonNull Executor executor,
@NonNull StabilityListener listener);
public void clearStabilityListener();
The tracker watches two streams of evidence: accessibility events flowing from
the controlled apps (onAccessibilityEvent) and a first-frame signal
(onFirstFrameReceived). Each input dispatch, app launch, or caller
interaction calls resetStabilityState(reason) with an @UnstableReason,
restarting the quiet period; when no further events arrive for the configured
duration, the tracker fires onSessionStable(). While the UI is churning it
reports onSessionUnstable(reason) so the agent knows why the screen is not
yet settled (for example, a caller interaction or an in-flight launch).
Because the timeout is caller-supplied rather than a fixed per-event-class
constant, an agent can tune it: short for snappy single-tap flows, longer for
cold-start app launches. The framework merely emits the signal; the agent
chooses whether to wait for onSessionStable() (for instance, before capturing
a post-action screenshot) or to proceed immediately.
51.3.28 AutomatedPackagesRepository and Launcher Indicators¶
AutomatedPackagesRepository
(frameworks/base/services/companion/java/com/android/server/companion/virtual/computercontrol/AutomatedPackagesRepository.java)
maintains the system-wide set of packages currently being driven by a
Computer Control session. It serves two consumers:
- Launchers register an
IAutomatedPackageListenerto learn which app icons should display an "agent is driving this app" indicator. The indicator is a UI affordance, not a security boundary; it tells the user which app the agent is currently controlling at a glance. - System UI uses the same data to render the global "agent active" status icon and to route notifications about automated activity.
The repository fires onAutomatedPackagesChanged(Set<String>) whenever the
set transitions. Each ComputerControlSessionImpl registers its
allowlisted packages on session start and unregisters them on session close.
The repository reference-counts each package, so it only exits the "automated"
state when the last referring session closes — robust to a future increase in
MAXIMUM_CONCURRENT_SESSIONS above its current value of 1.
This is the user-transparency contract the framework commits to. An automated app is always visually distinguishable from a user-driven one, even when the agent and user are interleaving control through the interactive mirror (51.3.13). The user is never left guessing whether something happening on screen was their tap or the agent's.
51.3.29 IME Integration: IRemoteComputerControlInputConnection¶
When the typing path is enabled, insertText() must route text into the
focused input field through the standard IME pipeline so that input
validation, autocorrect, password masking, and accessibility events all
fire the same way they would for a soft-keyboard tap. The mechanism is
IRemoteComputerControlInputConnection.aidl
(frameworks/base/core/java/com/android/internal/inputmethod/IRemoteComputerControlInputConnection.aidl).
The flow:
- The agent calls
ComputerControlSession.insertText(text, replaceExisting, commit). - The call arrives at
ComputerControlSessionImplon the system-server side. - The impl looks up the
IRemoteComputerControlInputConnectionfor the session's display inInputMethodManagerService.UserData.mComputerControlInputConnectionMap(frameworks/base/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java), keyed by the client's self-reported display ID. - The remote connection wraps the focused window's
InputConnectionand forwards the text throughcommitText(),setComposingText(), anddeleteSurroundingText()— the same methods a soft keyboard would use. - The target app sees text arrive through its normal
InputConnectioncallback, indistinguishable in shape from a soft-keyboard caller.
Keying the map by display ID matters because each Computer Control session
owns its own trusted display. In Android 17 MAXIMUM_CONCURRENT_SESSIONS is
1, so a single session is live at a time; the display ID still disambiguates
which session's text routes where and keeps the design ready for a larger cap.
When the InputConnection path is unavailable, insertText() falls back to
injecting key events via the VirtualDpad device. The fallback is
functional but loses the IME's text-shaping behavior — autocorrect does not
run, password fields are not masked at input time, and a target app that
filters input in onTextChanged sees character-at-a-time events rather than a
batched commit.
51.3.30 Feature Flag Set¶
Computer Control ships gated behind a set of aconfig flags in the
virtual_devices namespace
(frameworks/base/core/java/android/companion/virtual/flags/flags.aconfig).
They cleanly separate the rollout of independent features. In Android 17 the
flags are:
| Flag | Gates |
|---|---|
computer_control_access |
Core feature: the ACCESS_COMPUTER_CONTROL permission and the session API |
computer_control_per_app_consent |
The per-app consent model for sessions (51.3.24) |
computer_control_role_assistant_requirement |
Requires the ASSISTANT role to create a session |
computer_control_managed_profiles |
Computer Control support inside managed (work) profiles |
computer_control_cross_device |
Cross-device Computer Control sessions |
computer_control_support_v5 |
The Computer Control "v5" API surface |
A device can ship Computer Control's core surface (computer_control_access
on) without committing to every policy layer — useful for staged rollout, where
the per-app consent model and the assistant-role requirement land
incrementally. Conversely, a device can ship with all flags on for a full
posture from day one.
The flag set is also useful as a roadmap reading: a reader who finds Computer
Control at an unfamiliar stage of evolution can inspect which flags are on
(adb shell device_config get virtual_devices <flag_name>) to determine which
features the running device actually supports, independent of what the API
surface advertises.
51.3.31 Companion-Device-Subsystem Anchoring¶
Computer Control is implemented as a subsystem of VirtualDeviceManager, not alongside it. Three architectural consequences follow:
- Code location. The server-side classes live under
frameworks/base/services/companion/(the VDM service tree), not underframeworks/base/services/core/. The package path embedsvirtual/computercontrol/to make the subordination explicit at the filesystem level. - Lifecycle owner.
VirtualDeviceManagerServiceowns theComputerControlSessionProcessorinstance and theAutomatedPackagesRepository. When VDM tears down — for example, when the last virtual device is released and VDM enters its idle path — Computer Control state tears down with it. Computer Control cannot outlive its parent. - Reuse of VDM primitives. Computer Control does not invent its own
display, input, or surface-capture stack. It composes the existing VDM
primitives (
VirtualDevice,VirtualDisplay,VirtualDpad,VirtualTouchscreen) under a Computer-Control-specific session policy. The Computer Control additions are narrow: the trust-flag combination on the display, the fixed product IDs on the inputs (51.3.21), the session-scoped consent and AppOps tracking (51.3.24–51.3.25), and the accessibility-proxy stability detector (51.3.27).
Chapter 52 walks the general VDM machinery: how a VirtualDevice is
constructed and registered, how virtual displays surface into
WindowManager, how virtual input events dispatch through InputDispatcher,
and how the broader companion-device ecosystem (BLE associations, remote
device authentication) sits alongside VDM. A reader interested in how
the trusted VirtualDisplay is wired into WindowManager and what
WindowManager does differently on it should follow that cross-reference.
A reader interested in why Computer Control composes those primitives the
way it does, and what user-transparency contracts the composition enforces,
stays in this chapter.
51.3.32 Known Consumers¶
The first widely-shipped consumer of Computer Control is the Gemini in
Android assistant. The internal codename for the agent loop is Bonobo
(the log prefix #bnb# appears in app traces); the agent runs in the AGSA
process (com.google.android.googlequicksearchbox) and consumes the
AOSP-public Computer Control API documented in this chapter. AOSP itself
does not ship a Computer Control agent —
frameworks/base/libs/computercontrol/ and the system-server package
described above are framework code, not application code. The agent is
GMS-side and is not part of this checkout, but its existence as the first
production Computer Control consumer is what shaped the API's current
surface.
Two patterns observable in the Gemini consumer are worth surfacing for any new Computer Control agent:
- Dual-path fallback with AppFunctions. The agent declares two
<uses-library>entries in its manifest:com.android.extensions.appfunctionsandcom.android.extensions.computercontrol. It prefers AppFunctions (the structured-API path of section 51.2) for apps that publishAppFunctionService-backed functions, and falls back to Computer Control for apps that don't. The same agent can drive both because the two frameworks compose at the SDK extension level: an agent links both, queriesAppFunctionManagerfirst, and uses Computer Control for the apps where the function discovery returns empty. - Live mirror as the user-trust surface. The agent renders the
InteractiveMirror(51.3.5) inside its chat UI so the user watches the actions in real time and can hand control back at any moment via the touch-forwarding path. This matches the framework's intent: Computer Control does not make the live view optional, it makes the absence of one the unusual case. An agent that runs Computer Control without a visible mirror is one the user is right to be suspicious of.
How the Bonobo agent loop composes with the Computer Control API documented in this chapter. The agent itself is GMS-side; the framework on the right is AOSP.
sequenceDiagram
participant Bonobo as Bonobo agent in AGSA
participant Server as Gemini server
participant Session as ComputerControlSession (51.3.3)
participant Mirror as MirrorView in chat UI (51.3.5)
loop until task complete or HAND_OVER
Bonobo->>Session: getScreenshot
Session-->>Bonobo: PNG bytes
Bonobo->>Server: upload screenshot over ProcessQuery stream
Server-->>Bonobo: action TAP / SCROLL / GO_BACK / INSERT_TEXT / WAIT / HAND_OVER
alt action is HAND_OVER
Bonobo->>Session: handOverApplications
else other action
Bonobo->>Session: tap / swipe / insertText / performAction
end
Session->>Mirror: render updated frame
Mirror-->>Bonobo: optional user touch forwarded back
end
The loop terminates when the server responds with HAND_OVER or when the
user takes manual control via the mirror. The action vocabulary
(TAP, SCROLL, GO_BACK, INSERT_TEXT, WAIT, HAND_OVER) maps
one-to-one onto the ComputerControlSession methods documented in 51.3.3
and the navigation performAction codes — the agent does not synthesize
inputs the framework does not expose, and every action the framework
accepts can be issued by the agent. The bidirectional ProcessQuery
stream is the gRPC channel the agent uses to upload screenshots and
receive actions; that stream is GMS-side and not part of this checkout,
but its shape matters because it explains the server-driven nature of
the loop: the agent is a thin executor that asks the server what to do
next after every observation.
Beyond Gemini, Computer Control is shipping first on the highest-end Pixel and Galaxy devices and broadening as the feature flags above ramp. New consumers adopting Computer Control should expect the API surface to remain stable along the lines described in this chapter while the policy layer (which flags are on by default) continues to tighten.
51.3.33 Android 17 Security and Capability Changes¶
Computer Control shipped (flag-gated) in Android 16, but Android 17 reworked several parts of it -- this is the "substantially expanded and reworked" of the section opener:
- Permission hardening. The gating permission
ACCESS_COMPUTER_CONTROLmoved from 16'sinternal|knownSigner(cert-pinned throughconfig_accessComputerControlKnownSigners) to 17'sinternal|privileged(core/res/AndroidManifest.xml:9531), backed by aprivapp-permissions-platform.xmlentry -- i.e. from signer-pinning to the privileged-app allowlist model. A new signature permissionMANAGE_COMPUTER_CONTROL_CONSENT(AndroidManifest.xml:9537) guards the consent-management surface. - Per-app consent. The consent model moved from 16's session/global
computer_control_consentflag to 17's per-target-appcomputer_control_per_app_consent(in the flag set of 51.3.30): the user now grants or denies an agent's access per target app rather than once for a whole session; the grant is recorded against the existingOPSTR_COMPUTER_CONTROLAppOp ("android:computer_control", which itself predates 17 -- the per-app gating is the 17 change). - Audio capture and injection. New server components
ComputerControlAudioCaptureandComputerControlAudioInjector(backed by aVirtualAudioDevicecreated inComputerControlSessionImpl) let a session capture and inject audio on its virtual device -- a capability absent in 16, extending the agent's observe/act loop past screen and input. - The "observe" half made explicit.
ComputerControlSession.getAccessibilityWindows()(core/java/android/companion/virtual/computercontrol/ComputerControlSession.java:798) is the accessibility-window read the agent uses to see structured UI -- the counterpart to the screenshot path -- delegating to the internalComputerControlAccessibilityProxy(a package-privateAccessibilityDisplayProxysubclass, 51.3.27).
The virtual-keyboard removal (text now routes through the IME path of 51.3.29) and the full six-flag set are covered in 51.3.29-51.3.30; the items above are the security-model and capability deltas layered on top in 17.
51.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
...
51.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
51.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 |
51.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).
51.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.
51.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";
51.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
51.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.
51.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
51.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.
51.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
51.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
51.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
51.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.
51.4.14 Streaming Inference Protocol Detail¶
The streaming API delivers partial inference results incrementally, which suits LLM token-by-token output well but is not LLM-specific:
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)));
});
}
51.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.
51.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
51.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.
51.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.
51.4.19 Typed Content and the Embedding / Image-Description Models (Android 17)¶
The ODI surface described above is the generic, Bundle-based processRequest path. Android 17
adds a typed layer on top, gated by the on_device_intelligence_26q2 flag
(packages/modules/NeuralNetworks/flags/ondevice_intelligence.aconfig:26), under
frameworks/base/packages/NeuralNetworks/framework/platform/java/android/app/ondeviceintelligence/:
- Structured multimodal content.
Content.javaand itsPartcarry typed pieces -- text, image, and audio -- with large blobs streamed byParcelFileDescriptorrather than copied through aBundle. A commonOnDeviceModelbase (withMODEL_STATUS_*states) and aModelDownloadCallbackgive models a uniform availability/download lifecycle. - Two concrete typed model families.
embedding/EmbeddingModel.javaproduces vector embeddings (anEmbeddingRequestofContentto anEmbeddingResponseofEmbeddingVectors, withgetDimension()/getSupportedModalities()), andimagedescription/ImageDescriptionModel.javaturns an image into a text description. TheOnDeviceIntelligenceManagergains matchinglist…Models()/generate…()entry points for both, all@FlaggedApi(FLAG_ON_DEVICE_INTELLIGENCE_26Q2).
These extend ODI from "run an opaque generative model over a Bundle" to "call a typed embedding or image-description model over structured multimodal content," which is the shape agentic features need for retrieval (embeddings) and visual grounding (image description) -- still inside the same sandboxed-inference isolation of 51.4.18.
51.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.
Android 17 also introduces a new, higher-level NPU access surface seeded under
frameworks/base/core/java/android/npumanager/. That subsystem (NpuManager) is
covered in its own chapter (Chapter 53); this section stays focused on NNAPI,
the long-standing C-level accelerator path that today's native ML workloads
still target.
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
51.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
51.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, "...");
51.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.
51.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 |
51.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
51.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;
};
51.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:
51.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.
51.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
51.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.
51.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
51.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
51.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 |
51.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
51.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()"]
51.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
51.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 |
51.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)
51.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:
51.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.
Android 17 status: deprecation. The ODP public API surface is now marked
@Deprecatedbehind the newodp_depreciation_enabledflag (packages/modules/OnDevicePersonalization/flags/ondevicepersonalization_flags.aconfig:78). The module and the architecture below still ship, but new code should not target the ODP APIs; the agentic/on-device-inference direction the platform is investing in is the OnDeviceIntelligence + ContentSafety family (51.4, 51.15). The section below documents the mechanism as it stands, with that deprecation in mind.
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
51.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
51.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
51.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.
51.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
51.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.
51.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
51.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
51.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"]
51.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.
51.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
51.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.
51.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 |
51.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)
51.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]
51.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.
51.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.
51.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
51.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:
51.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.
51.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
51.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
51.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
51.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
51.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
51.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
51.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 |
51.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();
51.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);
51.8.5 Search¶
SearchSpec spec = new SearchSpec.Builder()
.addFilterSchemas("Email")
.setRankingStrategy(SearchSpec.RANKING_STRATEGY_RELEVANCE_SCORE)
.build();
SearchResults results = session.search("important meeting", spec);
51.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
51.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
51.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
51.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
51.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
51.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
51.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
51.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.
51.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.
51.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.
51.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
51.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.
51.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.
51.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).
51.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
51.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 |
51.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
51.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
51.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
51.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
51.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.
51.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
51.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.
51.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)
51.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
51.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 |
51.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 |
51.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
51.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
51.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
51.10 Cross-Subsystem Architecture Patterns¶
51.10.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 |
51.10.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
51.10.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 |
51.10.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"));
51.10.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.
51.11 Evolution and Future Direction¶
51.11.1 Historical Timeline¶
gantt
title AOSP AI Feature Timeline
dateFormat YYYY
section Core ML
NNAPI (8.1) :2017, 2026
NpuManager (17, Chapter 53) :2025, 2026
section Intelligence
TextClassifier (8.0) :2017, 2026
Content Capture (10) :2019, 2026
AppPrediction (10) :2019, 2026
section Privacy
AdServices (13) :2022, 2026
OnDevicePersonalization (14) :2023, 2026
section Agents
OnDeviceIntelligence (15) :2024, 2026
AppFunctions (16, matured 17) :2024, 2026
Computer Control (16) :2025, 2026
The trend is clear: Android is evolving from passive intelligence (capturing and classifying) toward active agent capabilities (executing functions, controlling apps). Android 17 deepens the agent layer in particular: AppFunctions gains runtime registration, observation, and an access-management framework, and a dedicated NPU access surface (NpuManager, Chapter 53) begins to take shape beside NNAPI.
51.11.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["LLM / Generative Model<br/>(running on 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["Interaction Logging<br/>(statsd attribution)"]
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.
51.11.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) |
51.12 What Android 17 Changes in AppFunctions¶
The earlier sections already fold most of the Android 17 changes into the
running narrative. This section gathers the framework's 17-era maturation in one
place so the delta from the original Android 16 beta is explicit. Every claim
below is grounded in the framework source under
frameworks/base/core/java/android/app/appfunctions/ and
frameworks/base/services/appfunctions/.
51.12.1 Runtime (Dynamic) Function Registration¶
In Android 16 a target app could only expose functions statically: declare an
AppFunctionService component and ship a metadata XML. Android 17 adds runtime
registration behind FLAG_ENABLE_DYNAMIC_APP_FUNCTIONS. An app implements the
AppFunction interface and registers it from an Activity or Service:
// frameworks/base/core/java/android/app/appfunctions/AppFunction.java
public interface AppFunction {
void onExecuteAppFunction(
@NonNull ExecuteAppFunctionRequest request,
@NonNull CancellationSignal cancellationSignal,
@NonNull OutcomeReceiver<ExecuteAppFunctionResponse, AppFunctionException> callback);
}
// frameworks/base/core/java/android/app/appfunctions/AppFunctionManager.java
AppFunctionRegistration registration =
appFunctionManager.registerAppFunction(functionId, executor, appFunction);
// ... later, when the function should no longer be available:
registration.unregister();
The registration's lifetime is bounded by the registering Context. The
function is executable only while the registering process is unfrozen and the
Context is alive; the system holds a strong reference to the AppFunction and
logs a leak warning if the app forgets to unregister(). The
functionIdentifier must still match an entry in the app's application-level
android.app.appfunctions XML property, and the metadata's scope governs
whether a function is global (SCOPE_GLOBAL) or tied to a specific activity
(SCOPE_ACTIVITY). Activity-scoped functions can only be registered from an
Activity context. Server-side, runtime registrations live in
MultiUserDynamicAppFunctionRegistry, keyed per user; the system server invokes
them through the IAppFunctionExecutor the app passed at registration rather
than by binding a separate component.
51.12.2 Discovery, State, and Observation on AppFunctionManager¶
Android 17 moves discovery and state queries directly onto
AppFunctionManager, replacing ad-hoc AppSearch queries with typed APIs (all
guarded by FLAG_ENABLE_DYNAMIC_APP_FUNCTIONS and the discovery/execution
permissions):
| Method | Returns | Purpose |
|---|---|---|
searchAppFunctions(spec, ...) |
List<AppFunctionMetadata> |
Find functions matching an AppFunctionSearchSpec |
getAppFunctionStates(names, ...) |
List<AppFunctionState> |
Runtime state (enabled, visibility) by AppFunctionName |
getAppFunctionActivityStates(ids, ...) |
List<AppFunctionActivityState> |
Functions registered for given AppFunctionActivityIds |
observeAppFunctions(executor, observer) |
AppFunctionObservation |
Watch for metadata/state changes |
An AppFunctionName is the (package, identifier) pair that uniquely names a
function. observeAppFunctions returns an AppFunctionObservation the caller
cancels when done; the AppFunctionObserver receives two callbacks:
// frameworks/base/core/java/android/app/appfunctions/AppFunctionObserver.java
public interface AppFunctionObserver {
void onAppFunctionMetadataChanged(@NonNull Set<String> changedPackageNames);
void onAppFunctionStatesChanged(@NonNull Set<AppFunctionName> changedFunctionNames);
}
The intended flow is: register an observer, then call searchAppFunctions /
getAppFunctionStates to get the initial snapshot; thereafter, re-query only the
packages or function names the observer reports as changed. Server-side this is
driven by AppFunctionMetadataObserver, which fans AppSearch change
notifications out to both internal caches and client callbacks.
51.12.3 The Access-Management Framework¶
Android 17 turns ad-hoc allowlisting into a first-class access-management surface with three new permissions:
| Permission | Granted to | Effect |
|---|---|---|
EXECUTE_APP_FUNCTIONS |
Allowlisted agents | Execute functions in allowlisted targets |
EXECUTE_APP_FUNCTIONS_SYSTEM |
Privileged system agents | Execute in any target; bypasses the allowlist |
DISCOVER_APP_FUNCTIONS |
Agents | Discover/observe functions without executing them |
On top of permissions, the framework tracks a per (agent, target) access
state (ACCESS_REQUEST_STATE_GRANTED / DENIED / UNREQUESTABLE) and a set
of access flags (51.2.4). These are persisted not by AppFunctions itself but
by the permission subsystem's AppFunctionAccessService
(frameworks/base/services/permission/java/com/android/server/permission/access/appfunction/AppFunctionAccessService.kt),
which the AppFunctions service obtains as a LocalService. Apps and system UI
interact with it through AppFunctionManager:
getAccessRequestState(target)/getAccessFlags(...)/updateAccessFlags(...)revokeSelfAccess(target)for an agent to drop its own accessgetValidAgents()/getValidTargets()for the management UIcreateRequestAccessIntent(target)to drive the request flow
The management UI is reachable through new activity actions on
AppFunctionManager
(ACTION_MANAGE_APP_FUNCTION_ACCESS, ACTION_MANAGE_AGENT_APP_FUNCTION_ACCESS,
ACTION_MANAGE_TARGET_APP_FUNCTION_ACCESS, and the @SystemApi
ACTION_REQUEST_APP_FUNCTION_ACCESS), all gated by
FLAG_APP_FUNCTION_ACCESS_UI_ENABLED. The signed agent allowlist itself is
served by the platform AllowlistManager and read through
SystemAppFunctionAllowlistReader (51.2.16), replacing the Android 16-era
DeviceConfig + Settings.Secure model.
51.12.4 The App Interaction API¶
Android 17 also factors interaction provenance out of AppFunctions into a shared
App Interaction API (FLAG_ENABLE_APP_INTERACTION_API). The attribution type
moved from the appfunctions package to android.app.AppInteractionAttribution
(51.2.8), and AppFunctionManagerService optionally publishes an
AppInteractionService local service when the flag is on. This positions
attribution to be reused by interaction surfaces beyond AppFunctions while
keeping the same interaction-type vocabulary (USER_QUERY, USER_SCHEDULED,
OTHER).
51.12.5 Multi-Service Functions and Request/Response Logging¶
Two further 17 changes loosen and instrument the execution path described in 51.2:
- Multiple services per package. The single-
AppFunctionService-per-package model of 51.2 is relaxed: a package may now expose functions from severalAppFunctionServicecomponents, indexed and executed together.AppFunctionManagerServiceImplroutes these throughexecuteMultiServiceAppFunctionInternal(...)(line 652, dispatched from the execute path at line 532), gated byenable_multi_serviceand itsenable_multi_service_bugfixsibling (core/java/android/app/appfunctions/flags/flags.aconfig:45/62). - Persistent request/response logging. Gated by
enable_request_response_logging(flags.aconfig:38), two new components --AppFunctionRequestResponseLoggerandAppFunctionPersistentLogger-- record execute requests and responses per user (toapp_functions_request_response.log), held inAppFunctionManagerServiceImpl.mRequestResponseLoggerPerUser. This gives agent executions an audit/debug trail the 16 implementation did not keep.
51.12.6 The Allowlist Service and App-Interaction History¶
The access-management framework of 51.12.3 gained two supporting pieces, both new in 17:
- A standalone
AllowlistService. Rather than storing the agent allowlist inDeviceConfig/Settings.Secureas 16 did, 17 adds a dedicated system service (frameworks/base/services/allowlist/.../AllowlistService.java) published asContext.ALLOWLIST_SERVICEand started bySystemServeronly whenenableAppFunctionPermissionV2()is set (SystemServer.java:1692). AppFunctions consumes it through an LRU-cachedSystemAppFunctionAllowlistReader, and shell tooling can add/remove/clear allowlist entries. - Interaction history as a queryable provider. The App Interaction API (51.12.4) backs its
history in a per-user SQLite database surfaced through a read-only ContentProvider
(
AppInteractionHistoryProvider, authoritycom.android.appinteraction.history) guarded by theREAD_APP_INTERACTIONpermission, with a 7-day default retention. This generalizes and replaces 16's appfunctions-localAppFunctionAccessHistory*classes. The user-facing side of this is a new agent-activity timeline in the Permission Mainline module's PermissionController (AgentActivityItemunder.../appinteraction/, andAgentUsageDetailsFragmentunder.../appfunctions/ui/handheld/), which surfaces which agents accessed data over 24-hour and 7-day windows.
Discovery itself also grew a typed metadata hierarchy in 17 -- AbstractAppFunctionMetadata with
AppFunctionMetadata / AppFunctionPackageMetadata / AppFunctionSchemaMetadata, plus the
qualified-name type AppFunctionName -- joined from static (manifest-declared) and runtime
(dynamically registered, 51.12.1) sources by reader/AppFunctionMetadataReader. In all, the 17
AppFunctions flag set adds enable_dynamic_app_functions, enable_app_interaction_api,
enable_app_function_permission_v2, enable_request_response_logging, and
enable_multi_service (+ _bugfix) on top of 16's lone enable_app_function_manager.
51.13 AiSeal: Sealed On-Device AI Compute¶
The intelligence subsystems covered so far run on the host OS: the system server mediates them, but the model weights, the inference code, and the personal data they touch all live in ordinary Android processes that a sufficiently privileged host component could observe. Android 17 introduces AiSeal, a system service that closes that gap by hosting on-device AI payloads inside a protected virtual machine whose memory the host kernel cannot read. AiSeal is the platform plumbing that lets an app reach an AI agent, an inference engine, or a personal AppSearch database that the rest of the device is sealed out of.
The protected-VM machinery itself -- the Android Virtualization Framework (AVF),
microdroid, VirtualizationService, instance images, and protected-VM firmware
verification -- is the subject of Chapter 56 (Virtualization); this section
covers only the AiSeal host service that sits on top of it and the connect flow
an app uses to talk into the VM.
Source tree (Android 17):
frameworks/base/core/java/android/aiseal/
AiSealManager.java -- @SystemApi host-side client
AiSealException.java -- Checked failure type
IAiSealHostService.aidl -- Host service: connectService(name)
aiseal.aconfig -- Flag android.aiseal.aiseal_host_apis
frameworks/base/services/aiseal/java/com/android/server/aiseal/
AiSealSystemService.java -- SystemService, bridges per-user lifecycle into the VM
frameworks/native/services/aisealhostservice/
src/main.rs -- Native Rust host service (runs inside the AVF VM)
src/config.rs -- AiSeal config + protected_vm flag parsing
src/payload.rs -- Payload (tenant APK) loading
src/package_manager.rs -- Calling-package resolution for ownership checks
src/instance_data.rs -- VM storage directory and image files
src/vsock_selinux.rs -- vsock connect with per-tenant SELinux MLS level
aidl/com/android/internal/aiseal/IAiSealInternalService.aidl
-- Per-user lifecycle (onUserUnlocking/Stopped/Removed)
aisealhostservice.rc -- init service definition
51.13.1 What AiSeal Is¶
AiSeal hosts a single protected virtual machine that runs several AI-related
payloads behind a sealed boundary. AiSealManager's own documentation describes
the VM's tenants as an AppSearch database for personal data that should not be
accessible from the host OS, an on-device AI inference service for processing
that data with large models, and AI agents that resolve user requests using it
(frameworks/base/core/java/android/aiseal/AiSealManager.java). In other words,
AiSeal is confidential on-device compute: it is where Android 17 can run an
assistant's reasoning over a user's private data with a hardware-enforced
guarantee that the host platform cannot inspect the computation.
Two terms recur. A tenant is a package whose code and configuration are
loaded into the VM as a payload. An exported service is a vsock endpoint a
tenant publishes inside the VM (via AVmPayload_runVsockRpcServer) and names in
the AiSeal configuration file; the matching host application -- the package
that owns the tenant -- reaches that service from outside the VM through
AiSealManager.connectService(name).
AiSeal is gated three ways. It is a @SystemApi guarded by the flag
android.aiseal.aiseal_host_apis (aiseal.aconfig); it requires the system
feature PackageManager.FEATURE_AISEAL ("android.software.aiseal"); and the
device property service.aiseal.enable must be set. AiSealManager.isEnabled()
checks the feature and the property together before any connection is attempted.
Because the VM does not model Android's profile separation, AiSealManager is
documented as accessible only by the primary user; secondary-user requests must
be routed through the primary user.
51.13.2 The Connect Flow¶
A host application connects to a sealed service through one method,
AiSealManager.connectService(String name), which is annotated @WorkerThread
(it may block) and requires android.permission.MANAGE_AISEAL_VIRTUAL_MACHINE.
The request crosses three boundaries: from the app into the system-published
aiseal_host binder, then over a vsock connection into the native host service
running inside the VM, and finally to the tenant's own vsock service.
The following diagram shows the connect path and the two services the in-VM native host service registers.
graph TB
subgraph host["Host OS"]
APP["Host App<br/>(tenant owner)"]
ASM["AiSealManager<br/>(@SystemApi, connectService name)"]
SS["AiSealSystemService<br/>(system_server)"]
SM["ServiceManager<br/>(aiseal_host / aiseal_internal)"]
end
subgraph avf["AVF (see Chapter 56)"]
VS["VirtualizationService"]
end
subgraph vm["Protected VM (microdroid)"]
HOST["aisealhostservice<br/>(Rust, IAiSealHostService)"]
INT["aiseal_internal<br/>(IAiSealInternalService)"]
GA["Guest agent<br/>(unlocks CE storage)"]
TEN["Tenant vsock service<br/>(exported by name)"]
end
APP --> ASM
ASM -->|"connectService(name) via aiseal_host binder"| HOST
HOST -->|"vsock connect to tenant port"| TEN
HOST -. "ParcelFileDescriptor (vsock fd)" .-> ASM
SS -->|"per-user lifecycle over aiseal_internal"| INT
INT --> GA
VS -->|"hosts / verifies"| vm
HOST -. "add_service" .-> SM
INT -. "add_service" .-> SM
Inside connectService, AiSealManager resolves the aiseal_host binder
(Context.AISEAL_HOST_SERVICE == "aiseal_host", via
ServiceManager.waitForService) and calls its single AIDL method,
IAiSealHostService.connectService(name), which returns a
ParcelFileDescriptor wrapping the vsock connection. The manager wraps the call
in VirtualMachine.binderFromPreconnectedClient(...) so the returned descriptor
is adopted as an RPC-binder client to the in-VM service. On the VM side, the
native host service (frameworks/native/services/aisealhostservice/src/main.rs)
implements connectService by: enforcing MANAGE_AISEAL_VIRTUAL_MACHINE;
looking the requested name up in the service-to-owner map built from the AiSeal
config; checking that the calling package owns that tenant (system_server and
root may call any service); and finally opening a vsock connection to the
tenant's declared port with a derived SELinux context. The ownership check is
the key isolation property: a host app can connect only to services exported by
the tenant it owns, never to another tenant's.
The native service is launched by init only when sealing is on -- the
aisealhostservice.rc file declares the service disabled and enables it on
property:sys.boot_completed=1 && property:service.aiseal.enable=1, stopping it
again if service.aiseal.enable goes to 0. At startup main.rs reads
service.aiseal.enable, waits for boot completion, connects to
VirtualizationService, loads the tenant payload, starts the VM, and only then
registers the aiseal_host and aiseal_internal binders with add_service.
51.13.3 Per-User CE-Key (kekFile) Handling¶
AiSeal stores per-user personal data in an encrypted database inside the VM, and
that storage must be locked and unlocked in lockstep with Android's
credential-encrypted (CE) storage on the host. The bridge is
AiSealSystemService
(frameworks/base/services/aiseal/java/com/android/server/aiseal/AiSealSystemService.java),
a SystemService registered in SystemServer.java (guarded by both the system
feature PackageManager.FEATURE_AISEAL and the flag
android.aiseal.Flags.aisealHostApis()). It connects to the aiseal_internal
binder and forwards three user-lifecycle events over
IAiSealInternalService:
| Host callback | Forwarded call | In-VM effect |
|---|---|---|
onUserUnlocking(user) |
onUserUnlocking(userId, kekFile) |
Guest agent unlocks that user's CE storage in the VM |
onUserStopped(user) |
onUserStopped(userId) |
Guest agent locks that user's CE storage |
onUserRemoved (broadcast) |
onUserRemoved(userId) |
Guest agent destroys that user's CE storage |
The unlocking path is where the key-encryption key (KEK) crosses the boundary.
On onUserUnlocking, AiSealSystemService computes a per-user file path under
the host's CE system directory -- Environment.buildPath(getDataSystemCeDirectory(userId), "AiSeal", "kek") -- creates the directory, runs SELinux.restorecon
on it, and passes the path (not the key bytes) to the VM via
IAiSealInternalService.onUserUnlocking(userId, kekFilePath). Inside the VM the
internal service wraps that path in an ICEStoreKEK binder and hands it to the
guest agent's userUnlocked(userId, kek). The guest agent calls back through
ICEStoreKEK.getKEK() to read the key from the (host-side, CE-protected) file,
or onKEKCreated(key) to write a freshly generated key back. Because the KEK
file lives under the user's CE directory, it is only readable while that user is
unlocked on the host -- so the VM's per-user encrypted storage is cryptographically
tied to the same lock state as the rest of the user's data. If the connection to
aiseal_internal is not yet established when a user unlocks, the service records
the user in an mUnlockedUsers set and replays the unlock once the VM service
connects.
51.13.4 Protected VM vs Nonprotected Fallback¶
Whether the AiSeal VM is a protected VM is governed by the device property
service.aiseal.protected_vm, read in config.rs
(AISEAL_PROTECTED_VM_FLAG). The default is true: a protected VM whose guest
memory is inaccessible to the host kernel and hypervisor, which is the entire
point of "sealing." Setting the property to false requests a nonprotected
VM -- the same payload and the same connect flow, but without the
memory-confidentiality guarantee. The flag is plumbed straight through to AVF as
the protectedVm field of VirtualMachineAppConfig when main.rs starts the
VM. The nonprotected mode exists chiefly for development and for devices whose
hardware lacks protected-VM support; production sealing relies on the protected
default. The deeper question of how a protected VM actually keeps its memory
private from the host -- pKVM, stage-2 page protection, and protected-VM firmware
attestation -- is covered in Chapter 56.
51.14 PersonalContext: On-Device Personal Context in the PCC¶
Where AiSeal seals AI compute inside a VM, PersonalContext seals a different
asset -- a structured, on-device store of the user's personal context -- inside
Android's Private Compute Core (PCC) sandbox. PersonalContext
(packages/apps/PersonalContext/, package com.android.personalcontext) is a
privileged, platform-signed app introduced in Android 17 that builds an
on-device personal-context surface: it observes signals the user already sees
(conversations, notifications, contacts), distills them into searchable
"memories," and serves context back to assistant and intelligence features --
all while keeping the raw data inside the PCC boundary.
51.14.1 What PersonalContext Is¶
PersonalContext is the reference implementation of the personal-context
framework whose SystemApi surface lives under
frameworks/base/core/java/android/service/personalcontext/. It registers three
ContextUnderstanderService subclasses
(packages/apps/PersonalContext/src/com/android/personalcontext/understander/):
| Service | Hint it consumes |
|---|---|
ChatUnderstanderService |
Conversation hints (chat content captured from messaging UIs) |
NotificationUnderstanderService |
Posted-notification hints |
ContextMenuUnderstanderService |
Context-menu / selection hints |
Each extends the platform base
android.service.personalcontext.understander.ContextUnderstanderService,
declares the action android.service.personalcontext.UnderstanderService, and is
protected by BIND_CONTEXT_COMPONENT_SERVICE so only the platform may bind it.
An understander overrides onInitializeFilter() to declare which hint types it
needs (for example, ChatUnderstanderService requires
ContentCaptureConversationHint) and onUnderstand(hints) to turn those hints
into ContextInsight objects -- displayable recalls or actionable suggestions.
The whole feature is gated by the product-container flag enable_osi
("on-device system intelligence"), the master flag in
packages/apps/PersonalContext/aconfig/personal_context.aconfig, alongside the
platform feature flag enable_personal_context_service that guards the
framework permissions.
51.14.2 Where It Sits in the PCC / On-Device-Intelligence Model¶
PersonalContext is a Private Compute Core app. Every one of its components --
the understander services, the WorkManager plumbing, the initialization
receiver -- carries android:privateComputeCore="@bool/enable_personal_context_pcc"
in its manifest, and the platform default product overlay sets that boolean to
true. The privateComputeCore manifest attribute
(frameworks/base/core/res/res/values/attrs_manifest.xml) marks a component as
running inside the PCC sandbox, where it is denied general network egress; data
leaves only through the narrow, audited PCC egress APIs.
The permissions PersonalContext holds are all signature-level
(signature|privileged, with PERSONAL_CONTEXT_READ_SETTINGS additionally
recents) and carry the allowedInPrivateComputeCore permission flag
(frameworks/base/core/res/AndroidManifest.xml):
| Permission | Role |
|---|---|
PERSONAL_CONTEXT_RECEIVE_HINTS |
Receive hints delivered to understander/refiner services |
PERSONAL_CONTEXT_PUBLISH_INSIGHTS |
Publish the insights an understander produces |
PERSONAL_CONTEXT_READ_SETTINGS |
Read personal-context settings |
USE_ON_DEVICE_INTELLIGENCE |
Drive the on-device inference path (see 51.4) |
That last permission is the link back to the rest of this chapter:
PersonalContext is a consumer of OnDeviceIntelligence (51.4). It uses the
sandboxed inference path to run the language models that summarize a
conversation or rank a recall, and it indexes the results -- not the raw source
data -- for retrieval. The hint inputs themselves originate from the same
passive-intelligence layer described in 51.7: a ContentCaptureConversationHint,
for instance, is built from Content Capture of a messaging surface. PersonalContext
thus stitches together three subsystems this chapter already covered -- Content
Capture as the source, OnDeviceIntelligence as the reasoning engine, and
AppSearch as the store -- into one personal-context pipeline.
The store is AppSearch-backed. PersonalContext converts understood data into
AppSearch GenericDocuments and indexes them through its MemoryIndexManager
(packages/apps/PersonalContext/src/com/android/personalcontext/storage/appsearch/MemoryIndexManager.kt),
then retrieves them at query time through a MemorySearchAgent
(.../search/MemorySearchAgentImpl.kt) that combines keyword and embedding-based
semantic search. The following diagram shows data flowing into and out of the
context store.
graph LR
subgraph sources["Passive sources (51.7)"]
CC["Content Capture<br/>(conversations)"]
NOTIF["Notifications"]
end
subgraph pcc["PersonalContext (PCC sandbox)"]
US["ContextUnderstanderService<br/>(Chat / Notification / ContextMenu)"]
ODI["OnDeviceIntelligence<br/>(USE_ON_DEVICE_INTELLIGENCE, 51.4)"]
MIM["MemoryIndexManager"]
MSA["MemorySearchAgent<br/>(keyword + semantic)"]
STORE["AppSearch memory store"]
end
CONS["Assistant / intelligence<br/>feature (ContextInsight)"]
CC -->|"hints"| US
NOTIF -->|"hints"| US
US -->|"summarize / rank"| ODI
ODI --> MIM
MIM -->|"GenericDocument"| STORE
STORE --> MSA
MSA -->|"recalls / actions"| US
US -->|"publish insights"| CONS
51.14.3 Privacy Posture¶
PersonalContext's privacy model is the PCC model, applied to a deliberately
sensitive data set. Three properties hold it together. First, sandbox
confinement: because its components are privateComputeCore, the app cannot
reach the network with the raw context it has gathered; the only sanctioned way
out is the PCC egress path, and the only thing it publishes are
derived ContextInsights, not source content. Second, on-device reasoning:
all summarization and ranking run through OnDeviceIntelligence's sandboxed
inference (51.4), so the personal data is processed locally rather than shipped
to a server. Third, access gating: every hint, insight, and settings
permission is signature|privileged and additionally flagged
allowedInPrivateComputeCore, so only platform-signed components participate,
and the whole surface can be disabled by clearing enable_osi. The data store
is the user's own AppSearch database, subject to the same per-user visibility
and access controls described in 51.8. The net effect mirrors AiSeal's goal from
the other direction: AiSeal seals compute in a VM, while PersonalContext seals
data and its processing in the PCC sandbox, and the two represent Android 17's
two complementary answers to running intelligence over private data without
leaking it.
51.15 ContentSafety: On-Device Content-Safety Inference¶
Android 17 adds ContentSafety, a new platform service that lets a privileged caller ask
an OEM-provided, on-device model whether a piece of content is sensitive or unsafe, without
the content ever leaving the device. It is the safety-layer sibling of OnDeviceIntelligence
(51.4): same "broker + sandboxed inference" shape, but purpose-built for content classification
rather than general generative inference. The whole feature is gated by the enable_contentsafety
flag (frameworks/base/core/java/android/app/contentsafety/flags/contentsafety.aconfig,
namespace ondevicesafety) and only starts when an OEM ships an implementation.
51.15.1 The Client API¶
ContentSafetyManager
(frameworks/base/core/java/android/app/contentsafety/ContentSafetyManager.java) is the
@SystemService(Context.CONTENT_SAFETY_SERVICE) entry point, annotated
@FlaggedApi(FLAG_ENABLE_CONTENTSAFETY). A caller holding the CHECK_CONTENT_SAFETY
permission submits a requestCheckContent(...) (the @RequiresPermission annotation is on every
public method, e.g. lines 83/100/116/258), and gets back one of two success codes --
CONTENT_SAFETY_SUCCESS_NONE (nothing sensitive) or CONTENT_SAFETY_SUCCESS_SENSITIVE (lines
149/153) -- or an error. A companion requestIsFeatureEnabled(...) lets a caller probe whether a
given safety feature/model is available before using it.
51.15.2 The Broker and the Sandbox¶
The work is split across two OEM-implemented services, exactly mirroring the dual-service isolation of 51.4.4:
ContentSafetyService(frameworks/base/core/java/android/service/contentsafety/ContentSafetyService.java) is the privileged broker, bound by the system withBIND_CONTENT_SAFETY_SERVICE. It does no inference itself; it manages the lifecycle of the sandboxed and settings services (onNotifySandboxedServiceConnected/Disconnected,onNotifySettingsServiceConnected/Disconnected, lines 230-244) and supplies feature/model data on demand (onGetFeatureRequest, line 218).ContentSafetySandboxedService(.../service/contentsafety/ContentSafetySandboxedService.java) is the isolated inference process, bound withBIND_SANDBOXED_CONTENT_SAFETY_SERVICE. Its two abstract hooks are where the model actually runs:onLoadFeatureRequest(...)(line 241) loads a model/feature, andonCheckContentRequest(...)(line 224) classifies the content. Running it in an isolated sandbox keeps the (untrusted-input-handling) model away from the privileged broker's process, the same containment principle as the ODI sandboxed inference service.
A third ContentSafetySettingsService carries configuration. On the framework side,
ContentSafetyManagerService
(frameworks/base/services/core/java/com/android/server/contentsafety/ContentSafetyManagerService.java)
is the SystemService that publishes the manager and orchestrates each check: in
checkContentInternal it fetches feature/model data from the broker (requestGetFeature) and then
calls the sandboxed service (requestCheckContent) itself, so the privileged broker never invokes
the sandbox directly. SystemServer starts it only when an OEM has
defined the implementation and the flag is on (startContentSafetyManagerService,
SystemServer.java:2126/3672, which logs "ContentSafetyManagerService not defined by OEM or
disabled by flag" otherwise). The caller permission CHECK_CONTENT_SAFETY and the two bind
permissions BIND_CONTENT_SAFETY_SERVICE / BIND_SANDBOXED_CONTENT_SAFETY_SERVICE are declared
in core/res/AndroidManifest.xml (lines 9784, 9795, 9805).
graph TD
APP["Caller: ContentSafetyManager.requestCheckContent()<br/>(holds CHECK_CONTENT_SAFETY)"] --> MS["ContentSafetyManagerService<br/>(system_server, orchestrates)"]
MS -->|"requestGetFeature"| SVC["ContentSafetyService<br/>(privileged broker, BIND_CONTENT_SAFETY_SERVICE)"]
SVC -->|"feature/model files"| MS
MS -->|"requestCheckContent"| SAND["ContentSafetySandboxedService<br/>(isolated, BIND_SANDBOXED_CONTENT_SAFETY_SERVICE)"]
SAND -->|"result"| RES["Result: SUCCESS_NONE or SUCCESS_SENSITIVE"]
RES --> APP
style SAND fill:#9C27B0,color:#fff
style RES fill:#4CAF50,color:#fff
In the agentic landscape this is the on-device guardrail an assistant or AppFunctions agent can consult before surfacing or acting on generated/processed content, with the classification model sandboxed and the content never leaving the device.
51.16 Try It¶
Exercise 51-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 51-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 51-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 51-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 51-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 51-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 51-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 51-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 51-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 51-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 51-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 51-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 51-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 51-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 51-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 51-16: AppFunction Access Management via ADB¶
In Android 17 the AppFunctions shell command exposes the access-management
surface directly (subcommands defined in AppFunctionManagerServiceShellCommand):
# Add agents to the additional (test) allowlist, on top of the device allowlist
adb shell cmd app_function set-additional-allowlisted-agents \
com.example.agent
# List the agents and targets the framework currently considers valid
adb shell cmd app_function list-valid-agents
adb shell cmd app_function list-valid-targets
# Grant an agent access to a target's functions
adb shell cmd app_function grant-app-function-access \
--agent-package com.example.agent \
--target-package com.example.noteapp
# Revoke that access
adb shell cmd app_function revoke-app-function-access \
--agent-package com.example.agent \
--target-package com.example.noteapp
# Enable or disable a specific function on the target
adb shell cmd app_function set-enabled \
--package com.example.noteapp --function createNote --state enable
# Drop cached allowlist decisions, and clear the additional agents
adb shell cmd app_function purge-allowlist-cache
adb shell cmd app_function clear-additional-allowlisted-agents
There is no longer a Settings.Secure allowlist string or an access-history
content provider; agent eligibility comes from the platform AllowlistManager
(51.2.16) and interactions are recorded to statsd (51.2.8).
Exercise 51-17: Implement AppFunction with Attribution¶
// Caller side: include attribution in request
AppInteractionAttribution attribution =
new AppInteractionAttribution.Builder(
AppInteractionAttribution.INTERACTION_TYPE_USER_QUERY)
.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
AppInteractionAttribution attribution = request.getAttribution();
if (attribution != null) {
Log.d(TAG, "Interaction type: " + attribution.getInteractionType());
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 51-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 51-19: Computer Control with Mirror Display¶
// Create a session with a mirror for human observation
ComputerControlSession session = ...; // from callback
// The extension library's MirrorView hosts the mirror inside the agent's own UI
// (it may only be attached to a secure, trusted display).
MirrorView mirrorView = findViewById(R.id.agent_mirror);
// Obtain a platform InteractiveMirror for the session and attach it to the view.
InteractiveMirror mirror = session.createInteractiveMirror(
a11yEmbeddedConnectionReceiver);
// Let the human take over interactively while the agent watches:
mirror.setInteractive(true);
mirror.resize(720, 1280);
// When done, clean up
mirror.close();
session.close();
The Android 17 InteractiveMirror exposes setInteractive, resize,
updateInsets, and close; there is no sendTouchEvent on the mirror. User
touches flow through the mirror surface when it is interactive; agent actions
still go through tap/swipe/insertText on the session.
Exercise 51-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 51-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 .
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, and matured substantially in Android 17 with runtime
function registration, discovery/state/observation APIs, and an
access-management framework. The framework uses GenericDocument (from
AppSearch) as its wire format, enforces access through a layered
permission/allowlist model (now served by the platform AllowlistManager), and
logs each agent-to-app interaction to statsd. 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.
AiSeal (new in Android 17) hosts on-device AI payloads -- an inference
service, AI agents, and a personal AppSearch database -- inside a protected
virtual machine the host cannot inspect, exposing a host-side
AiSealManager.connectService() over vsock and tying the VM's per-user
encrypted storage to host CE-key lock state (the protected-VM machinery lives in
Chapter 56).
PersonalContext (new in Android 17) is a Private Compute Core app that turns
captured conversations, notifications, and selections into searchable on-device
"memories" via ContextUnderstanderServices, reasoning with OnDeviceIntelligence
and storing results in AppSearch while the PCC sandbox keeps the raw data from
leaving the device.
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 |
| InteractiveMirror | frameworks/base/core/java/android/companion/virtual/computercontrol/InteractiveMirror.java |
| ComputerControlSessionImpl | frameworks/base/services/companion/java/com/android/server/companion/virtual/computercontrol/ComputerControlSessionImpl.java |
| AppFunctionException | frameworks/base/core/java/android/app/appfunctions/AppFunctionException.java |
| AppInteractionAttribution | frameworks/base/core/java/android/app/AppInteractionAttribution.java |
| AppFunction (runtime) | frameworks/base/core/java/android/app/appfunctions/AppFunction.java |
| AppFunctionManagerService | frameworks/base/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java |
| SystemAppFunctionAllowlistReader | frameworks/base/services/appfunctions/java/com/android/server/appfunctions/allowlist/SystemAppFunctionAllowlistReader.java |
| MultiUserDynamicAppFunctionRegistry | frameworks/base/services/appfunctions/java/com/android/server/appfunctions/dynamic/MultiUserDynamicAppFunctionRegistry.java |
| AppFunctionAccessService | frameworks/base/services/permission/java/com/android/server/permission/access/appfunction/AppFunctionAccessService.kt |
| AllowlistManager | frameworks/base/core/java/android/os/allowlist/AllowlistManager.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/ |
| AiSealManager | frameworks/base/core/java/android/aiseal/AiSealManager.java |
| IAiSealHostService.aidl | frameworks/base/core/java/android/aiseal/IAiSealHostService.aidl |
| AiSealException | frameworks/base/core/java/android/aiseal/AiSealException.java |
| aiseal.aconfig | frameworks/base/core/java/android/aiseal/aiseal.aconfig |
| AiSealSystemService | frameworks/base/services/aiseal/java/com/android/server/aiseal/AiSealSystemService.java |
| AiSeal host service (Rust) | frameworks/native/services/aisealhostservice/src/main.rs |
| AiSeal config (protected_vm) | frameworks/native/services/aisealhostservice/src/config.rs |
| IAiSealInternalService.aidl | frameworks/native/services/aisealhostservice/aidl/com/android/internal/aiseal/IAiSealInternalService.aidl |
| aisealhostservice.rc | frameworks/native/services/aisealhostservice/aisealhostservice.rc |
| PersonalContext AndroidManifest | packages/apps/PersonalContext/AndroidManifest.xml |
| ChatUnderstanderService | packages/apps/PersonalContext/src/com/android/personalcontext/understander/ChatUnderstanderService.kt |
| ContextUnderstanderService (framework) | frameworks/base/core/java/android/service/personalcontext/understander/ContextUnderstanderService.java |
| MemoryIndexManager | packages/apps/PersonalContext/src/com/android/personalcontext/storage/appsearch/MemoryIndexManager.kt |
| MemorySearchAgentImpl | packages/apps/PersonalContext/src/com/android/personalcontext/search/MemorySearchAgentImpl.kt |
| personal_context.aconfig | packages/apps/PersonalContext/aconfig/personal_context.aconfig |
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 | Set of signed packages permitted to be AppFunction agents, served by the platform AllowlistManager |
| Secure Aggregation | Cryptographic protocol that aggregates updates without revealing individuals |
| Differential Privacy | Mathematical guarantee that individual contributions are obscured by noise |