跳转至

IEvccApiSession 会话接口

最后更新:

AIDL:com.kooo.evcc.evccapi.IEvccApiSession

契约状态 值
引入版本 EvccApi 1.0
最近接口变更 EvccApi 1.2
稳定性 稳定公开接口
弃用状态 未弃用

IEvccApiSession 是应用通过 IEvccApi.openSession() 获得的授权会话。每个会话固定绑定建立它的 UID、 包名、签名和授权修订号;撤权、换签、云端关闭或 Binder 断开都会使旧会话失效。

本接口的所有方法都是同步 Binder 调用。除 getSessionInfo()、queryCapabilities() 和 close() 等轻量 调用外,统一放到工作线程执行。业务失败写入返回模型的 status;Binder 传输失败另行捕获 RemoteException。

完整签名

interface IEvccApiSession {
    EvccSessionInfo getSessionInfo();
    List<EvccCapability> queryCapabilities();
    EvccPropertyRecord resolveProperty(in EvccPropertySelector selector);
    EvccPropertyRecord readProperty(in EvccPropertySelector selector, boolean forceRefresh);
    List<EvccPropertyRecord> readProperties(
        in List<EvccPropertySelector> selectors,
        boolean forceRefresh
    );
    EvccOperationResult replaceSubscriptions(in List<EvccPropertySelector> selectors);
    EvccPropertyPage queryPropertyDirectory(in EvccPageRequest request);
    List<EvccSourceState> querySourceStates();
    EvccOperationResult writeProperty(in EvccWriteRequest request);
    List<EvccOperationResult> writeProperties(in List<EvccWriteRequest> requests);
    List<EvccVariableRecord> queryVariables();
    EvccVariableRecord readVariable(String name);
    EvccOperationResult replaceVariableSubscriptions(in List<String> names);
    EvccOperationResult writeVariable(in EvccVariableWriteRequest request);
    EvccOperationResult deleteVariable(String name);
    EvccOperationResult executeAction(in EvccActionRequest request);
    EvccOperationResult executeShell(in EvccShellRequest request);
    EvccOperationResult cancel(String requestId);
    void close();
    // API 1.1
    EvccMusicState readMusicState();
    EvccOperationResult setMusicSubscription(boolean enabled);
    EvccMusicResource openMusicLyrics(String trackId, long revision);
    EvccMusicResource openMusicArtwork(String trackId, long revision);
    EvccOperationResult sendMusicCommand(in EvccMusicCommand request);
    // API 1.2
    EvccVolumeGroupsResult queryVolumeGroups();
    EvccVolumeGroupState readVolumeGroup(int groupId);
    EvccVolumeWriteResult setVolumeGroup(in EvccVolumeSetRequest request);
    EvccVolumeWriteResult adjustVolumeGroup(in EvccVolumeAdjustRequest request);
}

通用返回规则

  • EvccOperationResult.isSuccess 只在 OK 或 ACCEPTED 时为 true。Shell 等待确认时返回的 PENDING 不属于这个快捷判断。
  • message 是诊断文本,不作为稳定分支条件。
  • 记录型返回先检查外层 status,再读取 descriptor、value 或 items。
  • 单条或整批结果超过 Binder 安全预算时,服务会裁剪值或返回 PAYLOAD_TOO_LARGE。字段定义见 数据模型参考。

getSessionInfo()

用途:读取当前会话身份、实际授权权限和授权修订号。

EvccSessionInfo getSessionInfo();

调用属性与权限:同步、轻量;只要求当前调用 UID 与有效会话一致,不需要分类权限。

请求:无参数。

同步返回:EvccSessionInfo。程序应以 grantedPermissions 为准,不能仅根据 preset 推断权限。 正常返回包含 sessionId、已验证的 clientPackage、权限 ID 列表、授权预设、grantRevision 和会话创建 时间。没有异步回调。

异常/状态:本类型没有 status 字段。授权在调用时失效,服务会关闭会话;返回值可能不再包含有效 身份。Binder 断开则抛出 IPC 异常。

val info = session.sessionInfo
if (EvccApiContract.Permission.PROPERTY_READ !in info.grantedPermissions) {
    // 提示用户在 EVCC 中授予属性读取权限。
}

注意事项:grantRevision 只用于判断授权是否变化。权限变化后旧会话会关闭,不能在旧 Binder 上继续 使用新的权限集合。

queryCapabilities()

用途:发现当前会话实际可用的权限、属性源和动作能力。

List<EvccCapability> queryCapabilities();

调用属性与权限:同步、轻量;只要求有效会话,不需要分类权限。

请求:无参数。

同步返回:List<EvccCapability>,列表包含三类 ID:

ID 形式 示例 available 的含义
权限 ID property.read 当前会话是否已获该权限
来源能力 source.vhal、source.apvp 当前来源是否不是 UNAVAILABLE
动作能力 action.app.launch 当前动作存在且会话具有其权限

没有异步回调。列表顺序不是协议。

异常/状态:会话失效时返回空列表或发生 Binder 异常;单个能力不可用通过 EvccCapability.available = false 表示,不是方法整体失败。

val capabilities = session.queryCapabilities()
val canRead = capabilities.firstOrNull { it.id == "property.read" }?.available == true

注意事项:不要把 EVCC 版本号或授权预设当作能力判断。reason 用于展示,id 和 available 才是 程序判断字段。

resolveProperty(selector)

用途:只解析属性身份,确定实际来源、规范 ID、区域和候选项,不读取当前值。

EvccPropertyRecord resolveProperty(in EvccPropertySelector selector);

调用属性与权限:同步;需要 property.directory。服务等待上限约 15 秒,并受每 UID 4 个、全局 12 个阻塞调用并发限制。

请求格式:

参数 类型 规则
selector EvccPropertySelector 不能为 null;id 与精确 name 至少提供一个

id 必须在无符号 32 位范围内。source 可为 AUTO 或明确来源;areaId 为空时使用自动区域规则。 字段及解析模式见属性模型与读写。

同步返回:EvccPropertyRecord。成功时 status = OK,descriptor 和 resolvedSource 有效,value 通常为空;发生来源或区域歧义时从 candidates 读取候选项。没有异步回调。

可能状态:OK、INVALID_ARGUMENT、NOT_FOUND、AMBIGUOUS_SOURCE、AMBIGUOUS_AREA、 PERMISSION_DENIED、RATE_LIMITED、TIMEOUT、PAYLOAD_TOO_LARGE。

val record = session.resolveProperty(
    EvccPropertySelector(name = "VehicleSpeed")
)
if (record.status == EvccApiContract.Status.OK) {
    val source = record.resolvedSource
    val area = record.descriptor?.areaId
}

注意事项:AUTO 按 VHAL → APVP → VEHICLE_AIDL → CAPI → ADAPT_API → SOA 解析。ALL_MATCHES 也不会 改变本方法的单记录返回类型;需要客户端选择时,候选放在 candidates 中。

readProperty(selector, forceRefresh)

用途:解析并读取一个属性的当前类型化值。

EvccPropertyRecord readProperty(
    in EvccPropertySelector selector,
    boolean forceRefresh
);

调用属性与权限:同步;需要 property.read。服务等待上限约 15 秒,受阻塞调用并发限制。

请求格式:

参数 类型 规则
selector EvccPropertySelector 同 resolveProperty()
forceRefresh boolean false 优先使用已初始化缓存;true 要求访问选定上游刷新

同步返回:EvccPropertyRecord。先检查 record.status,成功后再按 record.value.type 读取对应的 类型字段。value.initialized = false 表示尚未取得真实初值,不能把空文本当作属性值。没有异步回调。

可能状态:OK、PENDING、UNAVAILABLE、NOT_FOUND、AMBIGUOUS_SOURCE、 AMBIGUOUS_AREA、INVALID_ARGUMENT、PERMISSION_DENIED、RATE_LIMITED、TIMEOUT、 PAYLOAD_TOO_LARGE。

val record = session.readProperty(
    EvccPropertySelector(id = 557_849_089L),
    false,
)
val value = record.value
if (record.status == EvccApiContract.Status.OK && value?.initialized == true) {
    // 根据 value.type 读取 longValue、doubleValue、booleanValue 或 stringValue。
}

注意事项:强制刷新会增加上游请求,不应作为高频轮询方式。普通读取在无初值、来源换代或明确要求 刷新时才访问上游;需要持续更新时使用订阅。

readProperties(selectors, forceRefresh)

用途:一次读取多个属性,减少 Binder 往返。

List<EvccPropertyRecord> readProperties(
    in List<EvccPropertySelector> selectors,
    boolean forceRefresh
);

调用属性与权限:同步;需要 property.read。最多 100 项,整批等待上限约 30 秒,受阻塞调用并发 限制。

请求格式:

参数 类型 规则
selectors List<EvccPropertySelector> null 按空列表处理;最多 100 项
forceRefresh boolean 对本批所有选择器使用同一缓存/刷新策略

同步返回:正常执行时与请求顺序一致,每个选择器对应一个 EvccPropertyRecord。如果整批在进入执行 前被拒绝、限流或超时,服务可能只返回一个没有对应选择器的失败记录;因此不能无条件按下标映射。没有 异步回调。

如果整批结果超过 Binder 安全预算,服务返回能够容纳的有序前缀,并在末尾追加一个 status = PAYLOAD_TOO_LARGE、没有对应选择器的哨兵记录。该记录表示后续项目被省略,不是原请求中的 一个属性。

可能状态:每项与 readProperty() 相同;整批还可能以单条 INVALID_ARGUMENT、 PERMISSION_DENIED、RATE_LIMITED 或 TIMEOUT 失败。

val records = session.readProperties(
    listOf(
        EvccPropertySelector(id = 557_849_089L),
        EvccPropertySelector(name = "VehicleSpeed"),
    ),
    false,
)

注意事项:本方法不是快照事务,不保证不同来源的值来自同一时刻。遇到 PAYLOAD_TOO_LARGE 时缩小批次;不要原样重试同一超大请求。

replaceSubscriptions(selectors)

用途:用一个新集合原子替换本会话全部属性监听需求,并为每个成功项产生初始快照。

EvccOperationResult replaceSubscriptions(
    in List<EvccPropertySelector> selectors
);

调用属性与权限:同步提交;同时需要 property.listen 和 property.read。最多保留 256 个物理监听, 初始化等待上限约 30 秒,受阻塞调用并发限制。

请求格式:

参数 类型 规则
selectors List<EvccPropertySelector> null 或空列表清除监听;重复物理目标会去重

同步返回:EvccOperationResult。

  • OK:全部成功;acceptedCount 是实际接受数。
  • ACCEPTED:至少一项成功但存在失败或超过 256 项;检查 acceptedCount、failedCount 和 message。
  • FAILED:没有任何选择器成功解析。

异步回调:每个成功目标先通过 onPropertyEvent(..., INITIAL_SNAPSHOT) 给出基线,之后发送 VALUE_CHANGED;来源状态变化通过 onSourceStateChanged(),AUTO 来源切换使用 SOURCE_CHANGED。 回调是 oneway,可能在调用线程处理同步返回前已经进入客户端队列。

可能状态:OK、ACCEPTED、FAILED、PERMISSION_DENIED、RATE_LIMITED、TIMEOUT、 SESSION_CLOSED。

val result = session.replaceSubscriptions(
    listOf(EvccPropertySelector(id = 557_849_089L))
)
if (!result.isSuccess) {
    // 订阅集合没有成功替换,保留当前业务基线并处理状态。
}

注意事项:这是“整集合替换”,不是追加。空列表用于释放需求。AUTO 目标解析后会粘滞于实际来源; 瞬时断线先等待恢复,只有来源关闭或持续不支持时才尝试切换。SDK 会在重连和重同步时重放最后一次成功 提交的集合。

queryPropertyDirectory(request)

用途:分页查询属性目录;可选返回当前值,或显式发起高成本实时全量扫描。

EvccPropertyPage queryPropertyDirectory(in EvccPageRequest request);

调用属性与权限:同步;基础权限为 property.directory。includeValues = true 另需 property.read;mode = LIVE_FULL_SCAN 另需独立授权 property.full_scan。服务调用等待上限约 20 秒。

请求格式:

字段 规则
requestId 调用方关联 ID
pageToken 第一页为空;后续使用上页 nextPageToken,最长 32 字符
snapshotToken 第一页为空;续页必须回传首页面令牌,最长 128 字符
source AUTO 表示全部已开启来源,或指定一个来源;最长 64 字符
query 按属性目录文本筛选,最长 512 字符
limit 1~500;Raw AIDL 超界值会被服务夹到范围内,SDK 会在本地拒绝
includeValues 是否包含 EvccValue
mode DIRECTORY 或 LIVE_FULL_SCAN

同步返回:EvccPropertyPage。status = OK 时消费 items;只以 nextPageToken.isEmpty() 判断结束。后续页必须保持 source、query、mode 和 includeValues 不变。 没有异步回调。

可能状态:OK、INVALID_ARGUMENT、PERMISSION_DENIED、RATE_LIMITED、TIMEOUT、 PAYLOAD_TOO_LARGE。失败页通过页级 status/message 表示,不要只检查 items 是否为空。

var pageToken = ""
var snapshotToken = ""
do {
    val page = session.queryPropertyDirectory(
        EvccPageRequest(
            requestId = "directory-1",
            pageToken = pageToken,
            snapshotToken = snapshotToken,
            source = EvccApiContract.Source.AUTO,
            limit = 100,
        )
    )
    check(page.status == EvccApiContract.Status.OK)
    if (snapshotToken.isEmpty()) snapshotToken = page.snapshotToken
    page.items.forEach { /* 使用 descriptor */ }
    pageToken = page.nextPageToken
} while (pageToken.isNotEmpty())

注意事项:稳定目录快照约保留 60 秒,但可能因资源预算提前回收;令牌过期后从第一页重查。服务可 因 Binder 字节预算返回少于 limit 的项目。LIVE_FULL_SCAN 最长约 15 秒、同一应用每 30 秒最多 发起一次,并可能临时扩大 APVP 订阅;并发扫描会合并,成功结果短期缓存约 10 秒。

querySourceStates()

用途:查询五个属性源当前是否启用、是否已连接及连接代次。

List<EvccSourceState> querySourceStates();

调用属性与权限:同步、轻量;需要 property.directory。

请求:无参数。

同步返回:List<EvccSourceState>。每项包含 source、state、展示消息、sourceGeneration 和 生成时间。允许时当前实现按 VHAL、APVP、车辆 AIDL、CAPI、AdaptAPI、SOA 顺序返回,但客户端不应依赖顺序。 没有异步回调。

异常/状态:没有页级状态;权限不足或会话失效时返回空列表。来源自己的状态通常为 AVAILABLE、 PENDING 或 UNAVAILABLE。

val statesBySource = session.querySourceStates().associateBy { it.source }

注意事项:PENDING 可表示来源已开启但尚未连接;本方法不会要求第三方管理来源生命周期。持续监听 期间的相关状态变化通过 onSourceStateChanged() 发送。

writeProperty(request)

用途:解析并写入一个可写属性,或仅预检查权限、目标和写入能力。

EvccOperationResult writeProperty(in EvccWriteRequest request);

调用属性与权限:同步;需要 property.write。服务等待上限约 15 秒,受阻塞调用并发限制。实际写入 按 UID 令牌桶限流:可突发 100 项,之后约每秒恢复 10 项;validateOnly 不计费。

请求格式:

字段 规则
requestId 建议使用唯一 ID;返回时最多保留 128 字符
selector 写入目标;AUTO 允许选择来源
value STRING/LONG/DOUBLE/BOOLEAN/JSON/BYTES_BASE64 之一,类型字段必须一致
validateOnly true 只检查,不产生上游写入

规范化后的写入文本最长 16,384 字符。NULL 不能作为写入类型。

同步返回:EvccOperationResult。OK 表示上游已接受/发送写入,不保证车辆执行结果会有单独回执; 本方法没有异步完成回调。

可能状态:OK、INVALID_ARGUMENT、PERMISSION_DENIED、RATE_LIMITED、TIMEOUT、 NOT_FOUND、AMBIGUOUS_SOURCE、AMBIGUOUS_AREA、UNAVAILABLE、UNSUPPORTED、FAILED。

val result = session.writeProperty(
    EvccWriteRequest(
        requestId = "write-speed-mode-1",
        selector = EvccPropertySelector(id = 0x1234L),
        value = EvccValue.ofLong(1L),
        validateOnly = true,
    )
)

注意事项:建议先以 validateOnly = true 预检查,再由用户明确触发实际写入。AUTO 一旦选定实际来源, 写入失败不会向下一来源重放,避免同一操作被执行两次。

writeProperties(requests)

用途:按顺序提交多个属性写入。

List<EvccOperationResult> writeProperties(
    in List<EvccWriteRequest> requests
);

调用属性与权限:同步;需要 property.write。最多 100 项,整批等待上限约 30 秒,受阻塞调用和属性 写入令牌桶限制。配额按本批 validateOnly = false 的项目数在执行前扣取。

请求格式:requests 为有序 EvccWriteRequest 列表;null 按空列表处理。

同步返回:通常每个已执行请求对应一个结果并保持顺序。以下情况会出现单个或尾部汇总结果:

  • 整批超限、无权限或被限流:返回一个失败结果;
  • 执行期间撤权:已执行项保留,末尾结果说明剩余项未执行;
  • 达到总超时:已执行项保留,末尾 TIMEOUT 说明后续项未执行。

没有异步回调。

可能状态:逐项与 writeProperty() 相同;整批还可能出现汇总的 INVALID_ARGUMENT、 PERMISSION_DENIED、RATE_LIMITED 或 TIMEOUT。

val results = session.writeProperties(requests)
results.forEach { result ->
    // 用 requestId 关联;不要只按返回下标判断原请求。
}

注意事项:批量写入明确不是事务,没有回滚。前一项成功、后一项失败时,前一项不会撤销;也不会把 同一 AUTO 写入改投其他来源。

queryVariables()

用途:读取 EVCC 当前公开的全部用户变量和内置变量。

List<EvccVariableRecord> queryVariables();

调用属性与权限:同步;需要 variable.read。

请求:无参数。

同步返回:按变量名排序的 List<EvccVariableRecord>。存在的变量通常为 status = OK;读取每项时 还应检查 mutable,内置变量为只读。没有异步回调。

如果完整列表超过 Binder 预算,末尾会加入名称为 __evccapi_truncated__、状态为 PAYLOAD_TOO_LARGE 的哨兵记录,表示结果不完整。权限不足时返回仅含一个 PERMISSION_DENIED 记录的 列表。

val variables = session.queryVariables()
val truncated = variables.any {
    it.name == "__evccapi_truncated__" &&
        it.status == EvccApiContract.Status.PAYLOAD_TOO_LARGE
}

注意事项:变量值统一通过 EvccValue 返回;当前变量运行时由自动化、Lua 与开放 API 共用。不要把 查询结果中的展示文本作为变量类型协议。

readVariable(name)

用途:读取一个指定变量。

EvccVariableRecord readVariable(String name);

调用属性与权限:同步;需要 variable.read。

请求格式:

参数 类型 规则
name String 服务会去除首尾空白;必须匹配 [A-Za-z_][A-Za-z0-9_]{0,127}

同步返回:EvccVariableRecord。OK 表示存在,NOT_FOUND 表示名称合法但变量不存在。没有异步 回调。

可能状态:OK、NOT_FOUND、INVALID_ARGUMENT、PERMISSION_DENIED、 PAYLOAD_TOO_LARGE。

val record = session.readVariable("drive_mode")
if (record.status == EvccApiContract.Status.OK) {
    val text = record.value?.stringValue
}

注意事项:mutable = false 表示该变量不能通过开放 API 写入或删除;读取权限并不隐含写权限。

replaceVariableSubscriptions(names)

用途:用一个新集合替换本会话全部变量监听,并为每个名称发送初始快照。

EvccOperationResult replaceVariableSubscriptions(
    in List<String> names
);

调用属性与权限:同步提交;同时需要 variable.listen 和 variable.read。去重后最多 256 个名称。

请求格式:names 中每个名称会去除首尾空白,且必须匹配变量名规则。null 或空列表清除全部变量 监听。Raw AIDL 中空字符串会导致整次请求 INVALID_ARGUMENT;Client SDK 会先过滤空字符串。

同步返回:EvccOperationResult。OK 时 acceptedCount 为去重后的名称数。不存在的合法名称也可 订阅,其初始记录为 NOT_FOUND。订阅名单包含无效名称或超过 256 项时整次请求失败,不部分接受。

异步回调:每个名称先通过 onVariableEvent(..., INITIAL_SNAPSHOT) 发送基线,之后发送 VALUE_CHANGED 或 REMOVED。回调可能在调用方处理同步返回前进入客户端队列。

可能状态:OK、INVALID_ARGUMENT、PERMISSION_DENIED、SESSION_CLOSED。

val result = session.replaceVariableSubscriptions(
    listOf("drive_mode", "gps_speed")
)

注意事项:这是整集合替换,不是追加。用户变量通过运行时事件通知;时间、GPS、音乐状态等内置变量 由 EVCC 在存在监听需求时按约 1 秒间隔检查,第三方应用不需要管理其上游生命周期。

writeVariable(request)

用途:创建或更新一个用户变量,或只执行预检查。

EvccOperationResult writeVariable(
    in EvccVariableWriteRequest request
);

调用属性与权限:同步;需要 variable.write。

请求格式:

字段 规则
requestId 建议使用唯一关联 ID;返回最多保留 128 字符
name 合法变量名,且不能是 EVCC 内置只读变量
value 类型字段必须一致;服务转换为共用运行时的规范文本值
validateOnly true 只校验,不修改变量或消耗变更次数

单值最长 16,384 字符且 UTF-8 最大 64 KiB。公开 API 最多保留 512 个用户变量,总量最大约 2 MiB;如果 自动化/Lua 现有数据已超过预算,API 只允许保持或缩小占用。

同步返回:EvccOperationResult。OK 表示预检查通过或变量已经写入。没有单独完成回调;已监听该 名称的会话会收到变量变化事件。

可能状态:OK、INVALID_ARGUMENT、PERMISSION_DENIED、RATE_LIMITED、 PAYLOAD_TOO_LARGE。实际写入和删除共享每 UID 每分钟 120 次变更限额。

val result = session.writeVariable(
    EvccVariableWriteRequest(
        requestId = "variable-1",
        name = "drive_mode",
        value = EvccValue.ofString("sport"),
    )
)

注意事项:validateOnly 不能预留名称或容量。预检查成功后到实际写入前,授权、配额或全局变量占用 仍可能变化,实际调用必须再次处理返回状态。

deleteVariable(name)

用途:删除一个用户变量。

EvccOperationResult deleteVariable(String name);

调用属性与权限:同步;需要 variable.delete。与变量写入共享每 UID 每分钟 120 次变更限额。

请求格式:name 会去除首尾空白,必须匹配变量名规则,且不能是内置只读变量。

同步返回:EvccOperationResult。OK 表示已删除,NOT_FOUND 表示变量不存在。此方法没有 requestId 参数,所以返回的 requestId 为空。没有单独完成回调;已监听该名称的会话会收到 REMOVED。

可能状态:OK、NOT_FOUND、INVALID_ARGUMENT、PERMISSION_DENIED、RATE_LIMITED。

val result = session.deleteVariable("drive_mode")

注意事项:删除会影响同一 EVCC 进程内使用该用户变量的自动化、Lua 和其他已授权 API 客户端,调用 前应确认变量所有权和业务影响。

executeAction(request)

用途:执行 EVCC 明确列入能力目录的安全动作或高权限动作。

EvccOperationResult executeAction(in EvccActionRequest request);

调用属性与权限:同步。权限由 actionId 决定。实际执行按 UID 令牌桶限流:可突发 10 次,之后约 每秒恢复 2 次;validateOnly 不计费。

请求格式:

字段 规则
requestId 最长 128 字符,不能包含控制字符
actionId 最长 128 字符,不能包含空白/控制字符
argumentsJson 必须是 JSON 对象文本,最长 64 KiB
validateOnly true 只做动作存在性、权限与参数校验

API 1.0 当前动作:

actionId 所需权限 argumentsJson 结果
app.launch action.safe {"packageName":"com.example.app"} 启动目标应用的 Launcher Activity
home.show action.safe {} 返回系统桌面
toast.show action.safe {"text":"hello"} 显示短提示;展示文本最多使用前 500 字符
automation.trigger action.privileged {"ruleId":"..."} 触发当前正在运行的自动化规则

packageName 必须是合法的点分 Android 包名;packageName 和 ruleId 都必须非空且最长 512 字符。 toast.show 的 text 必须非空,实际展示最多取前 500 字符。未列出的额外 JSON 字段不构成稳定协议。

同步返回:EvccOperationResult。OK 表示动作已经同步提交/执行;没有异步完成回调。

可能状态:OK、UNSUPPORTED、INVALID_ARGUMENT、PAYLOAD_TOO_LARGE、 PERMISSION_DENIED、RATE_LIMITED、FAILED。

val result = session.executeAction(
    EvccActionRequest(
        requestId = "launch-1",
        actionId = "app.launch",
        argumentsJson = """{"packageName":"com.example.app"}""",
        validateOnly = false,
    )
)

注意事项:先通过 queryCapabilities() 检查 action.<actionId>。动作列表是显式白名单;不能把任意 自动化动作名直接当作 actionId。automation.trigger 还要求目标规则正在运行且 ID 存在。

executeShell(request)

用途:提交通过 EVCC 安全策略检查的自由输入 Shell 命令。该方法采用“同步接收 + 异步完成”两阶段 模型。

EvccOperationResult executeShell(in EvccShellRequest request);

调用属性与权限:同步提交;始终需要独立授权 shell.exec。被判定为系统设置、应用状态或系统服务 操作的命令还需要 shell.system_write,并由车机端逐次确认。同一 UID 在所有会话间同时只能有一个待 确认或执行中的 Shell 请求。

请求格式:

字段 规则
requestId 必填、当前会话任务中唯一、最长 128 字符且不能含控制字符
command 必填,最长 4096 字符;服务会规范化并逐段检查
timeoutMs 服务夹到 1,000~30,000 ms;模型默认 10,000 ms
includeRawOutput 1.0 预留字段;当前不改变返回结构,输出始终受授权和大小限制

同步返回:

状态 含义 下一步
ACCEPTED 命令已进入执行 等待 onShellCompleted()
PENDING 正等待车机端系统写入确认 等待确认及最终回调,或调用 cancel()
其他失败状态 未进入可执行流程 按状态修正请求或权限,不等待成功结果

EvccOperationResult.isSuccess 对 PENDING 为 false,因此 Shell 调用必须显式处理该状态。

异步回调:最终结果通过 onShellCompleted(EvccShellResult) 返回,用 requestId 关联。结果包含最终 状态、退出码、stdout、stderr、是否裁剪及耗时。stdout 与 stderr 原始合计最多 256 KiB,并会再按 Binder 预算裁剪。

executeShell()
    ├─ ACCEPTED ────────────────┐
    └─ PENDING → 车机端确认 ────┤
                                └→ onShellCompleted()

可能同步状态:ACCEPTED、PENDING、INVALID_ARGUMENT、PERMISSION_DENIED、 RATE_LIMITED、FAILED。最终回调常见 OK、FAILED、CANCELLED、PERMISSION_DENIED。

val requestId = "shell-${System.currentTimeMillis()}"
val accepted = session.executeShell(
    EvccShellRequest(
        requestId = requestId,
        command = "getprop ro.build.version.release",
        timeoutMs = 10_000,
    )
)
// ACCEPTED 或 PENDING 都不是最终执行结果;在回调中按 requestId 接收结果。

注意事项:公开 Shell 永久拒绝命令替换、变量展开、解释器二次执行、here-doc、通配展开、后台任务、 通用文件修改、进程终止、磁盘/分区/启动链操作、持久提权和资源耗尽等高风险形式。不要尝试编码或拼接 绕过;完整边界见Shell 安全边界。系统写入确认 30 秒内未处理会取消。

cancel(requestId)

用途:取消本会话提交的待确认或正在执行的 Shell 请求。

EvccOperationResult cancel(String requestId);

调用属性与权限:同步、轻量;只要求会话身份和授权仍有效,不额外要求 Shell 分类权限。若授权整体 已经变化,旧会话会关闭,并由服务端清理待确认或执行中的任务。

请求格式:requestId 会去除首尾空白,应与 executeShell() 的 ID 完全一致。

同步返回:

  • CANCELLED:待确认请求已取消,或运行中任务已收到取消请求;
  • NOT_FOUND:当前会话没有这个可取消请求;
  • PERMISSION_DENIED:会话身份或授权已失效。

运行中的任务在回调通道仍有效时通常还会发送最终 onShellCompleted(..., CANCELLED);Binder 已断开或 会话已关闭时不能依赖回调到达。

val result = session.cancel(requestId)

注意事项:CANCELLED 表示取消已经受理,不是对车机所有外部副作用的事务回滚。命令在收到取消前 已经完成的部分无法撤销。

close()

用途:由客户端主动关闭会话并释放服务端资源。

void close();

调用属性与权限:同步、轻量;只能由建立会话的 UID 调用,无分类权限。

请求/返回:无参数、无返回值。服务端移除属性、变量和音乐订阅,取消待确认/运行中的 Shell,解除 callback death 监听并释放会话。主动关闭不会再向同一回调发送 onSessionClosed()。

runCatching { session.close() }

异常与注意事项:仍需处理 Binder 已死亡时的 RemoteException。close() 后所有旧会话方法都应 视为无效;需要继续调用时重新绑定并通过根接口建立新会话。使用 EvccApiClient 时优先调用 SDK 的 disconnect() 或终态 close(),不要绕过 SDK 单独关闭其底层会话。

回调的线程、事件序号和重同步要求见IEvccApiCallback。

API 1.1 音乐方法

方法 返回 权限
readMusicState() EvccMusicState music.read
setMusicSubscription(boolean enabled) EvccOperationResult 开启需要 music.listen + music.read;关闭仅需有效会话
openMusicLyrics(String trackId, long revision) EvccMusicResource music.read
openMusicArtwork(String trackId, long revision) EvccMusicResource music.read
sendMusicCommand(in EvccMusicCommand request) EvccOperationResult music.control

均为同步 Binder 调用,应在工作线程执行。完整资源、控制、订阅与错误语义见音乐专题。

API 1.2 音量组方法

queryVolumeGroups() 和 readVolumeGroup(groupId) 需要 volume.read,成功返回 AVAILABLE。setVolumeGroup(request) 和 adjustVolumeGroup(request) 需要 volume.control,下发成功或预检查通过返回 OK。四者均无额外回调,均使用工作线程。完整请求、返回、配额和不确定结果处理见音量组。