시스템 속성을 API로 구현

시스템 속성을 사용하면 정보(보통 구성에 관한 정보)를 시스템 전체에 쉽게 공유할 수 있습니다. 각 파티션은 자체 시스템 속성을 내부적으로 사용할 수 있습니다. 파티션 전체에서 속성에 액세스가 가능하면(예: /system에 의해 정의된 속성에 액세스하는 /vendor 등) 문제가 발생할 수 있습니다. Android 8.0부터는 /system 등의 일부 파티션을 업그레이드할 수 있지만 /vendor는 변경되지 않습니다. 시스템 속성은 단순히 스키마가 없는 문자열 키-값 쌍의 전역적 사전에 불과하므로 속성을 안정화하기는 쉽지 않습니다. /system 파티션은 어떠한 알림도 없이 /vendor 파티션이 의존하는 속성을 변경하거나 제거할 수 있습니다.

Android 10 버전부터는 파티션 전체에 걸쳐 액세스된 시스템 속성이 Sysprop 설명 파일로 도식화되며, 속성에 액세스하기 위한 API가 C++ 및 Rust의 확정 함수와 Java의 클래스로 생성됩니다. 이러한 API는 액세스하는 데 특별한 문자열(예: ro.build.date)이 필요하지 않고 정적으로 입력이 가능하기 때문에 사용하기가 훨씬 편리합니다. ABI 안전성 역시 빌드 시간에 검사되며, 호환되지 않는 변경사항이 발생하면 빌드가 손상됩니다. 이 검사는 파티션 간에 명시적으로 정의된 인터페이스 역할을 합니다. 또한 이러한 API는 Rust, Java, C++ 간의 일관성도 제공합니다.

시스템 속성을 API로 정의

다음 스키마를 사용하여 protobuf의 TextFormat을 사용하는 Sysprop 설명 파일(.sysprop)을 포함하는 API로 시스템 속성을 정의합니다.

// File: sysprop.proto

syntax = "proto3";

package sysprop;

enum Access {
  Readonly = 0;
  Writeonce = 1;
  ReadWrite = 2;
}

enum Owner {
  Platform = 0;
  Vendor = 1;
  Odm = 2;
}

enum Scope {
  Public = 0;
  Internal = 2;
}

enum Type {
  Boolean = 0;
  Integer = 1;
  Long = 2;
  Double = 3;
  String = 4;
  Enum = 5;
  UInt = 6;
  ULong = 7;

  BooleanList = 20;
  IntegerList = 21;
  LongList = 22;
  DoubleList = 23;
  StringList = 24;
  EnumList = 25;
  UIntList = 26;
  ULongList = 27;
}

message Property {
  string api_name = 1;
  Type type = 2;
  Access access = 3;
  Scope scope = 4;
  string prop_name = 5;
  string enum_values = 6;
  bool integer_as_bool = 7;
  string legacy_prop_name = 8;
}

message Properties {
  Owner owner = 1;
  string module = 2;
  repeated Property prop = 3;
}

한 개의 Sysprop 설명 파일에는 하나의 속성 집합을 설명하는 한 개의 속성 메시지가 포함됩니다. 필드의 의미는 다음과 같습니다.

필드 의미
owner Platform, Vendor 또는 Odm 속성을 소유하는 파티션으로 설정됩니다.
module 생성된 API가 배치되는 네임스페이스(C++의 경우) 또는 정적 최종 클래스(Java의 경우)를 만드는 데 사용됩니다. 예를 들어 com.android.sysprop.BuildProperties는 C++의 네임스페이스 com::android::sysprop::BuildProperties, Java의 com.android.sysprop에 있는 패키지의 BuildProperties 클래스가 됩니다.
prop 속성 목록입니다.

Property 메시지 필드의 의미는 다음과 같습니다.

필드 의미
api_name 생성된 API의 이름입니다.
type 이 속성의 유형입니다.
access Readonly: getter API만 생성합니다.

Writeonce, ReadWrite: getter 및 setter API를 생성합니다.

참고: ro. 접두사가 붙은 속성은 ReadWrite 액세스를 사용하지 못할 수도 있습니다.

scope Internal: 소유자만 액세스할 수 있습니다.

Public: 모두가 액세스할 수 있습니다. NDK 모듈은 예외입니다.

prop_name 기본 시스템 속성의 이름입니다(예: ro.build.date).
enum_values (Enum, EnumList만 해당)잠재적인 enum 값으로 구성된, 막대(|)로 구분된 문자열입니다. value1|value2를 예로 들 수 있습니다.
integer_as_bool (Boolean, BooleanList만 해당) setter에 falsetrue 대신 01을 사용하도록 합니다.
legacy_prop_name (선택사항, Readonly 속성만 해당) 기본 시스템 속성의 기존 이름입니다. getter를 호출할 때 getter API는 prop_name 읽기를 시도하고 prop_name이 존재하지 않는 경우 legacy_prop_name을 사용합니다. 기존 속성에 대한 지원을 중단하고 새 속성으로 이전할 때는 legacy_prop_name을 사용합니다.

각 속성의 유형은 C++, Java, Rust의 다음 유형에 매핑됩니다.

유형 C++ Java Rust
불리언 std::optional<bool> Optional<Boolean> bool
정수 std::optional<std::int32_t> Optional<Integer> i32
UInt std::optional<std::uint32_t> Optional<Integer> u32
Long std::optional<std::int64_t> Optional<Long> i64
ULong std::optional<std::uint64_t> Optional<Long> u64
Double std::optional<double> Optional<Double> f64
문자열 std::optional<std::string> Optional<String> String
Enum std::optional<{api_name}_values> Optional<{api_name}_values> {ApiName}Values
T 목록 std::vector<std::optional<T>> List<T> Vec<T>

다음은 세 가지 속성을 정의하는 Sysprop 설명 파일의 예입니다.

# File: android/sysprop/PlatformProperties.sysprop

owner: Platform
module: "android.sysprop.PlatformProperties"
prop {
    api_name: "build_date"
    type: String
    prop_name: "ro.build.date"
    scope: Public
    access: Readonly
}
prop {
    api_name: "date_utc"
    type: Integer
    prop_name: "ro.build.date_utc"
    scope: Internal
    access: Readonly
}
prop {
    api_name: "device_status"
    type: Enum
    enum_values: "on|off|unknown"
    prop_name: "device.status"
    scope: Public
    access: ReadWrite
}

시스템 속성 라이브러리 정의

이제 Sysprop 설명 파일로 sysprop_library 모듈을 정의할 수 있습니다. sysprop_library는 C++, Java, Rust에서 API 역할을 합니다. 빌드 시스템은 sysprop_library의 각 인스턴스에 대해 한 개의 rust_library와 한 개의 java_library, 한 개의 cc_library를 내부적으로 생성합니다.

// File: Android.bp
sysprop_library {
    name: "PlatformProperties",
    srcs: ["android/sysprop/PlatformProperties.sysprop"],
    property_owner: "Platform",
    vendor_available: true,
}

API 검사를 진행하려면 소스에 API 목록 파일을 포함해야 합니다. 이렇게 하려면 API 파일과 api 디렉터리를 생성합니다. api 디렉터리를 Android.bp와 같은 디렉터리에 배치합니다. API 파일 이름은 <module_name>-current.txt, <module_name>-latest.txt입니다. <module_name>-current.txt는 현재 소스 코드의 API 서명을 보유하고 <module_name>-latest.txt는 최신 고정 API 서명을 보유합니다. 빌드 시스템은 빌드 시간에 이러한 API 파일을 생성된 API 파일과 비교하여 API 변경 여부를 검사하고 current.txt가 소스 코드와 일치하지 않는 경우 오류 메시지와 current.txt 파일 업데이트에 관한 안내를 표시합니다. 다음은 디렉터리 및 파일 구성의 예입니다.

├── api
│   ├── PlatformProperties-current.txt
│   └── PlatformProperties-latest.txt
└── Android.bp

Rust, Java, C++ 클라이언트 모듈은 sysprop_library에 연결하여 생성된 API를 사용할 수 있습니다. 빌드 시스템은 클라이언트에서 생성된 C++, Java, Rust 라이브러리로 연결되는 링크를 생성하여 클라이언트에게 생성된 API에 대한 액세스를 제공합니다.

java_library {
    name: "JavaClient",
    srcs: ["foo/bar.java"],
    libs: ["PlatformProperties"],
}

cc_binary {
    name: "cc_client",
    srcs: ["baz.cpp"],
    shared_libs: ["PlatformProperties"],
}

rust_binary {
    name: "rust_client",
    srcs: ["src/main.rs"],
    rustlibs: ["libplatformproperties_rust"],
}

Rust 라이브러리 이름은 sysprop_library 이름을 소문자로 변환하고 .-_로 교체한 후 앞에 lib를 추가하고 _rust를 추가하여 생성됩니다.

위의 예에서는 다음과 같이 정의된 속성에 액세스할 수 있습니다.

Rust 예시:

use platformproperties::DeviceStatusValues;

fn foo() -> Result<(), Error> {
  // Read "ro.build.date_utc". default value is -1.
  let date_utc = platformproperties::date_utc()?.unwrap_or_else(-1);

  // set "device.status" to "unknown" if "ro.build.date" is not set.
  if platformproperties::build_date()?.is_none() {
    platformproperties::set_device_status(DeviceStatusValues::UNKNOWN);
  }

  …
}

Java 예시:

import android.sysprop.PlatformProperties;

…

static void foo() {
    …
    // read "ro.build.date_utc". default value is -1
    Integer dateUtc = PlatformProperties.date_utc().orElse(-1);

    // set "device.status" to "unknown" if "ro.build.date" is not set
    if (!PlatformProperties.build_date().isPresent()) {
        PlatformProperties.device_status(
            PlatformProperties.device_status_values.UNKNOWN
        );
    }
    …
}
…

C++ 예시:

#include <android/sysprop/PlatformProperties.sysprop.h>
using namespace android::sysprop;

…

void bar() {
    …
    // read "ro.build.date". default value is "(unknown)"
    std::string build_date = PlatformProperties::build_date().value_or("(unknown)");

    // set "device.status" to "on" if it's "unknown" or not set
    using PlatformProperties::device_status_values;
    auto status = PlatformProperties::device_status();
    if (!status.has_value() || status.value() == device_status_values::UNKNOWN) {
        PlatformProperties::device_status(device_status_values::ON);
    }
    …
}
…