Extensible Time Management

预计 Android Automotive 操作系统 (AAOS) 将成为汽车中的智能时间源。成为汽车中的时间网关会带来独特的挑战。在某些车辆中,AAOS 必须充当其他板载系统的车辆时间源,并提供根据多个输入源(例如 TCU)智能地设置时钟的功能。

为了确保 Android 时间管理架构完好无损,本部分介绍了在平台级别解决的常见时间挑战。

*Extensible Time Management (ETM)* 提供了一种机制和 API,使合作伙伴能够在 AAOS 和 Android 外部的其他车载系统之间同步时间。此功能假定这些其他车载系统(包括 ECU、BCM 单元或仪表盘时钟)已通过 CAN、以太网或其他某些方式连接到 VHAL。因此,使 AAOS 能够与 VHAL 同步时间就足以实现核心目标。

此功能不会

  • 向 Android 添加多种类型的时间,例如将 SystemTimevDisplayTime 分开。
  • 处理 VHAL 如何从其他车载系统、ECU、BCM 或仪表盘时钟获取和设置时间值。这被视为每个 VHAL 的实现细节。该内容仅指定为了启用时间同步功能而需在 AAOS 和 VHAL 之间进行的交互。
  • 有关 TimeDetectorServiceTimeDetectorStrategy 的具体设计选择和实现细节,请参阅自动时间检测
  • 如果需要,您可以向车载设备添加多个时间源,然后将其整合到自定义 VHAL 实现中。然后,此 VHAL 会通过 EXTERNAL_CAR_TIME 属性传递一个合并时间值。
  • 我们未明确添加对时间精度标准的支持,例如 Android 中针对财务交易的 NIST 精度计时。OEM 必须为系统提供准确的时间源。

针对 Android 中的时间推出的若干增强功能

Android 12 支持通过提供的位置管理器服务从卫星中获取时间。借助时间源优先级配置叠加层,供应商可以提供叠加层以配置已启用时间建议的优先级。Android 核心时间源包括:

  1. 电话堆栈、网络身份和时区 (NITZ)
  2. 手动选择时间
  3. 简单网络时间协议 (SNTP)
  4. 全球导航卫星系统 (GNSS)(Android 12 中的新功能)
  5. ExternalTime(Android Automotive OS 中的新功能)

术语

本页面使用了以下术语:

术语 定义
车身控制模块 (BCM) 一个通用术语,表示负责监控和控制车辆内电子配件的电子控制单元。
控制器局域网 (CAN) 串行通信协议。
电子控制单元 (ECU) ECU 可以通过用户 HAL 属性与基于 Android 的信息娱乐系统连接。
全球导航卫星系统 (GNSS) 提供空间信号的卫星,将定位和时间数据发送给 GNSS 接收器。
美国国家标准与技术研究院 (NIST) 启用精确时间戳功能。
网络身份和时区 (NITZ) 一种通过无线网络向移动设备配置本地时间和日期、时区和 DST 偏移以及网络服务提供商身份信息的机制。
实时通信 (RTC) 任何模式的通讯,所有用户都可以即时交换信息,或者延迟时间可以忽略不计。
简单网络时间协议 (SNTP) 网络时间协议 (NTP) 的简化版本。
远程信息控制单元 (TCU) 指车辆的板载嵌入式系统,用于控制车辆的跟踪功能。

用于同步时间的新 VHAL 属性

Extensible Time Management 为 VHAL 添加了两个新属性,用于在 Android 与各种 ECU(或车载设备中安装的单元)之间同步时间:

  • EXTERNAL_CAR_TIME - 用于将时间从 ECU、BCM 或其他车载系统(AAOS 外部)同步到 AAOS。
  • EPOCH_TIME - 用于将时间从 AAOS 同步到 ECU、BCM 或其他车载系统。

VHAL 会确定单一时间源,并将其中继到其他单元。AAOS 不一定是时间的可信来源。

  • 如果选择 Android 作为时间的可信来源,VHAL 将支持使用只写属性 EPOCH_TIME 传达 Android 的时间变化,并将其传递给其他车载系统(例如 ECU 和 BCM)。
  • 如果选择 Android 作为时间的可信来源,VHAL 将支持使用只读属性 EXTERNAL_CAR_TIME 传达 Android 的时间变化,并将其传递给其他车载系统(包括 ECU 和 BCM 等)。
EXTERNAL_CAR_TIME EPOCH_TIME
(master 分支中的 ANDROID_TIME
VHAL > Android Android > VHAL

纪元时间 (Android)

如果此属性受支持,AAOS 会写入此属性,以将 Android 系统时间传递给 VHAL。AAOS 会在 CarServices 进行初始化时以及每次 AAOS 时间源发生更改(如 INTENT_ACTION_TIME_CHANGED 所示)时写入此属性一次。这对于将其他车载系统(例如仪表板时钟)和 ECU 与 Android 时间同步非常有用。VHAL 实现会从 Android 接收可传递到其他已连接系统和 ECU 的时间值和更新。如果此属性受支持,Android 通常是时间的可信来源。

新增了两个 VHAL 属性:EPOCH_TIME(用于从 Android 到 ECU 的通信)和 EXTERNAL_CAR_TIME(用于从 ECU 到 Android 的通信)。OEM 通常只需要实现其中一个属性。

下面列出了 AAOS 专用供应商、硬件、接口和自定义供应商时间源:

时段 (ToD) OEM 后端
简单网络时间协议 (SNTP) OEM 后端
板载 ECU(如 BCM),可能具有:

OEM 必须将这些时间源整合为单一时间,并使用 EXTERNAL_CAR_TIME 将这些时间源传递到 Android。然后,Android 会使用该值根据配置的优先级考虑其系统时间。

EPOCH_TIME

  /**
     * Current date and time, encoded as Unix time (in milliseconds).
     * This value denotes the number of milliseconds seconds that have
     * elapsed since 1/1/1970 UTC.
     *
     * AAOS will write to this value to give VHAL the Android system's time,
     * if the VHAL supports this property. This can be useful to synchronize
     * other vehicle systems (dash clock etc) with Android's time.
     *
     * AAOS writes to this property once during boot, and
     * will thereafter write only when some time-source changes are propagated.
     * AAOS will fill in VehiclePropValue.timestamp correctly.
     * Note that AAOS will not send updates for natural elapse of time.
     *     int64Values[0] = provided Unix time (in milliseconds)
     *
     * Note that the property may take >0 ms to get propagated through the stack
     * and, having a timestamped property helps reduce any time drift. So,
     * for all writes to the property, the timestamp can be used to negate this
     * drift:
     *     drift = currentTimeMillis - PropValue.timestamp
     *     effectiveTime = PropValue.value.int64Values[0] + diff
     *
     * Aside, this property could have been better named ANDROID_EPOCH_TIME, but it
     * continues to be called EPOCH_TIME for legacy reasons. We will try to fix
     * this naming discrepancy when we migrate to AIDL.
     *
     * @change_mode VehiclePropertyChangeMode:ON_CHANGE
     * @access VehiclePropertyAccess:WRITE_ONLY
     * @unit VehicleUnit:MILLI_SECS
     */
    EPOCH_TIME = (
        0xxxxx
        | VehiclePropertyGroup:SYSTEM
        | VehiclePropertyType:INT64
        | VehicleArea:GLOBAL),

外部车辆时间

如果是在编译时由 OEM 配置,Android 可以使用外部时间设置自己的系统时间。AAOS 将订阅此属性并从中读取数据,以生成 ExternalTimeSuggestion 并将其传递给 TimeManager 服务。这对于使 Android 时间与其他车载系统(例如仪表盘时钟)和 ECU 保持同步非常有用。VHAL 实现应在启动时以及时钟同步发生更改时写入此属性。VHAL 实现预计不会随时间的自然推移写入此属性。当此属性受支持时,Android 通常不是时间的可信来源。

  /**
     * Current date and time for the Car, encoded as Unix time (in milliseconds).
     * This value denotes the number of milliseconds seconds that have
     * elapsed since 1/1/1970 UTC.
     *
     * This property signals a change in CarTime to Android. VHAL must report the
     * most accurate current CarTime when this property is read, and publish a
     * change to this property when the CarTime value has changed.
     * Android will read and subscribe to this property to fetch time from VHAL,
     * if the property is supported. This can be useful to synchronize Android's
     * time with other vehicle systems (dash clock etc).
     *     int64Values[0] = provided Unix time (in milliseconds)
     *
     * Whenever a new Value for the property is received, AAOS will create
     * and send an "ExternalTimeSuggestion" to the "TimeDetectorService".
     * If other sources do not have a higher priority, Android will use this
     * to set the system time. For information on how to adjust time source
     * priorities and how time suggestions are handled (including how Android
     * handles gitter, drift, and minimum resolution) see Time Detector Service
     * documentation.
     *
     * Note that the property may take >0 ms to get propagated through the stack
     * and, having a timestamped property helps reduce any time drift. So,
     * for all writes to the property, the timestamp can be used to negate this
     * drift:
     *     drift = currentTimeMillis - PropValue.timestamp
     *     effectiveTime = PropValue.value.int64Values[0] + diff
     *
     * WARNING: It is recommended to use Android's own systems for GNSS, NTP,
     * Telephony etc. instead of wiring those through the VHAL using this property.
     *
     * @change_mode VehiclePropertyChangeMode:ON_CHANGE
     * @access VehiclePropertyAccess:READ_ONLY
     * @unit VehicleUnit:MILLI_SECS
     */
    EXTERNAL_CAR_TIME = (
        0xxxxx
        | VehiclePropertyGroup:SYSTEM
        | VehiclePropertyType:INT64
        | VehicleArea:GLOBAL),

ExternalTime 和 ExternalTimeSuggestion

我们为 TimeManager 服务添加了建议 ExternalTime 的新路径。TimeDetectorService(相应地是 TimeManager)使用多个不同的时间源。在 Android 中,目前可用于设置系统时钟的三个时间源是来自电话堆栈的网络身份和时区 (NITZ)、网络时间协议 (NTP) 以及用户手动提供的时间。

ExternalTime 是一个以 AAOS 平台为目标的新时间源。它有助于将 Android 时间与车载设备中(但仍在 Android 外部)的 ECU 或 BCM 单元同步。ExternalTime 以系统时间源的形式正确插入,具有可配置的优先级。在 TimeDetectorStrategy 配置中进行适当配置后,外部时间会优先于其他时间源。

以下序列图说明了新设计:

图 1:序列图

支持多种属性

OEM 可以选择同时支持 EPOCH_TIMEEXTERNAL_CAR_TIME 属性。

例如,假设车载设备将 ECU 连接到某个时间源(例如 DCF77),但必须向 Android 传递相应值,以便使用 Android 中的其他时间源(如 GNSS、NTP 或 Telephony)进行解析。在这种情况下,OEM 可以通过 EXTERNAL_CAR_TIME 将 ECU 中的时间值传递给 Android,并使用 EPOCH_TIME 接收解析后的系统时间。然后,使用该时间设置其他外围设备上的时间。

在 Android 系统和 VHAL 实现之间的信息流中避免任何反馈环至关重要。因此,对同时支持这两个属性的 OEM 的唯一限制是,通过 EXTERNAL_CAR_TIME 提供的值不应依赖从 EPOCH_TIME 接收到的值。

为 Intent.ACTION_TIME_CHANGED 注册 BroadcastReceiver

TimeHalService(汽车服务中的一个新类)会为 Intent.ACTION_TIME_CHANGED 注册 BroadcastReceiver,以便它向 VHAL 属性 EPOCH_TIME 写入数据,进而向 VHAL 通知 Android 系统时间的变化。

用于建议外部时间的新 TimeManager API

时间建议功能通过一个名为 android.app.time.TimeManager#suggestExternalTime() 的新 Android @SystemAPI 提供。此方法会将建议转发给 TimeDetectorService#suggestExternalTime,后者又将其转发给 TimeDetectorStrategy#suggestExternalTimeTimeDetectorStrategy 会跟踪上一个 ExternalTimeSuggestion 实例,如果外部时间源在时间检测器策略配置中设为优先,就会使用此建议。

新的 SUGGEST_EXTERNAL_TIME 权限

添加了一项名为 SUGGEST_EXTERNAL_TIME 的新权限。TimeManager.sugestExternalTime() API 受此权限控制,因此第三方开发者可以使用 SUGGEST_EXTERNAL_TIME 权限调用它。新权限会被标记为 privileged 保护级别。虽然这可能会导致系统映像上预安装的第三方应用获得此权限,但这是可以接受的。

  • 需要 OEM 协调才能向第三方应用授予此权限,因此添加“特权”不会带来很大的风险。
  • 此 API 不能保证建议的时间戳会立即用作新的系统时间戳。必须配置系统,使外部时间源的优先级高于任何其他时间源(例如 GNSS),才能使用 ExternalTimeSuggestion。此配置由 OEM 执行。
  • 应用可以直接使用 TimeManager.setTime() 设置系统时间,这需要 SET_TIME 权限。这一新权限和 API 模型也拥有特权,并且与现有 API 一致。

实现时间可扩展性

为确保高效地实现这项新功能,OEM 必须知道如何配置 AAOS 以利用新的 VHAL 属性,并正确传播相应值。本部分将提供给 OEM,让各个 OEM 都能决定他们要部署什么内容以及如何配置时间可扩展性。

配置 AAOS 时间源

AAOS 支持两个 VHAL 属性(EPOCH_TIMEEXTERNAL_CAR_TIME)以及可配置的 TimeDetectorStrategy,以帮助 OEM 确定各车载单元(包括 Android、ECR 和 BCM)之间的时间同步方式。首先,OEM 必须确定时间的单个可信来源。从 Android 的角度来看,可信来源是 Android 或外部时间源(可能是 ECU、BCM 或仪表盘时钟)。

在这种情况下,OEM 可能需要将其他车载系统(例如 ECU 和 BCM)与 AAOS 时间同步。为此,VHAL 必须支持只写属性 EPOCH_TIME,然后在 AAOS 将其系统时间写入此属性时处理或转发收到的更新。

具体流程如下所述:

  1. VHAL 通过 EPOCH_TIME VHAL 属性接收 Android 的系统时间。
  2. TimeHalService 在启动时以及 Android 中的时间源被同步时读取系统时间,从而写入该属性。
  3. VHAL 可将接收到的时间值传播到各种 ECU 和 BCM 单元。

当 Android 未用作时间源时

在这种情况下,OEM 可能需要将 AAOS 时间与作为时间可信来源的车载系统(例如 ECU 或 BCM)同步。为此,VHAL 必须支持只读属性 EXTERNAL_CAR_TIME,并且只要时间源发生变化或重新校准时钟,就会发布对该属性的更新。

EM 还必须确保外部时间在 TimeDetectorStrategy 配置中具有适当的优先级:

<!-- Specifies priority of automatic time sources. Suggestions from higher entries in the list take precedence over lower ones.
See com.android.server.timedetector.TimeDetectorStrategy for available sources. -->
     <string-array name="config_autoTimeSourcesPriority">
        <item>external</item>
        <item>gnss</item>
        <item>network</item>
        <item>telephony</item>
    </string-array>

此流程总结如下:

  1. VHAL 更新 EXTERNAL_CAR_TIME 属性。
  2. TimeHalService(在 CarServices 中)通过订阅读取该属性。
  3. TimeHalService 创建 ExternalTimeSuggestion 并将其发送到 TimeManager.
  4. TimeManager 将建议转发给 TimeDetectorService.
  5. TimeDetectorService 使用 TimeDetectorStrategy 选择新的系统时间。

附录 A:属性变更

EPOCH_TIME

TimeHalService 提供以下功能来支持 EPOCH_TIME,前提是系统报告该属性受 VHAL 支持:

  1. 在启动时将系统时间写入 EPOCH_TIME 属性。
  2. Intent.ACTION_TIME_CHANGED 注册 BroadcastReceiver,并在收到广播时将系统时间写入 EPOCH_TIME 属性。
public class TimeHalService extends HalServiceBase {
    …

    @Override
    public void init() {
        updateProperty(System.currentTimeMillis());

        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_TIME_CHANGED);

        mReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                if (Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) {
                    updateProperty(System.currentTimeMillis());
                }
            }
        };

        mContext.registerReceiver(
                mReceiver, filter, /* broadcastPermissions = */ null,
                new Handler(Looper.myLooper()));
    }

    private void updateProperty(long timeMillis) {
        VehiclePropValue propValue = new VehiclePropValue();
        propValue.prop = VehicleProperty.EPOCH_TIME;
        propValue.areaId = VehicleArea.GLOBAL;
        propValue.status = VehiclePropertyStatus.AVAILABLE;
        propValue.timestamp = timeMillis;
        propValue.value.int64Values.add(timeMillis);

        Slogf.d(TAG, "Sending Android Time: " + propValue);
        mHal.set(propValue);
    }
}

EXTERNAL_TIME

TimeHalService 提供以下功能来支持 EXTERNAL_CAR_TIME,前提是系统报告该属性受 VHAL 支持:

  1. 订阅 EXTERNAL_CAR_TIME 属性(如有)。
  2. 收到 EXTERNAL_CAR_TIME 的新值后,创建 ExternalTimeSuggestion 并将其发送到 TimeManager Service。
public class TimeHalService extends HalServiceBase {
    private static final float SAMPLE_FREQ_HZ = 1.0 / 60 / 60;

    …

    @Override
    public void init() {
        VehiclePropValue propValue = mHal.get(VehicleProperty.EXTERNAL_CAR_TIME);
        suggestExternalTime(propValue);

        mHal.subscribeProperty(this, EPOCH_TIME, SAMPLE_FREQ_HZ);
    }

    @Override
    public void onHalEvents(List<VehiclePropValue> values) {
        for (VehiclePropValue value : values) {
            suggestExternalTime(value);
        }
    }

    private void suggestExternalTime(VehiclePropValue value) {
        if (value.prop != VehicleProperty.EXTERNAL_CAR_TIME
             || value.status != VehiclePropertyStatus.AVAILABLE) {
            return;
        }

        long epochTime = value.value.int64Values.get(0);
        // timestamp is stored in nanoseconds but the suggest API uses
        // milliseconds.
        long elapsedRealtime = value.timestamp / 1_000_000;

        mLastExternalTimeSuggestion =
            new ExternalTimeSuggestion(elapsedRealtime, epochTime);

        Slogf.d(TAG, "Sending Time Suggestion: " + mLastExternalTimeSuggestion);
        mTimeManager.suggestExternalTime(mLastExternalTimeSuggestion);
    }
}