Treble Properties


Full treble?

To make a device treble-compatible, just set PRODUCT_FULL_TREBLE_OVERRIDE := true and be done? But what does that even mean?

For treble requirements, see build/make/core/


(PRODUCT_NOTICE_SPLIT is always true and cannot be altered)

Further requirements for devices launching on Oreo and up:


VNDK - Vendor Native Development Kit

Defining BOARD_VNDK_VERSION means the generated build will restrict access to libraries to only those explicitly made available to vendor programs. Usually you’d set it to current, meaning the latest VNDK version will be used.

You can test building with VNDK but disable it at runtime:


There is much more to to it, read about the VNDK at

Linker namespaces

Example full-treble config:


c.f. system/core/rootdir/

_enforce_vndk_at_runtime := false
    _enforce_vndk_at_runtime := true
ld_config_template := $(LOCAL_PATH)/etc/ld.config.txt

_enforce_vndk_lite_at_runtime := false
ifeq ($(_enforce_vndk_at_runtime),false)
    _enforce_vndk_lite_at_runtime := true
ld_config_template := $(LOCAL_PATH)/etc/ld.config.vndk_lite.txt

# for legacy non-treblized devices
LOCAL_SRC_FILES := etc/ld.config.legacy.txt

So, to conclude linker paths stuff:

  • Setting BOARD_VNDK_VERSION enforces usage of the latest ld.config.txt(ld.config.28.txt for Pie), unless BOARD_VNDK_RUNTIME_DISABLE is set. This means _enforce_vndk_at_runtime is set internally.
  • If _enforce_vndk_at_runtime is not set, PRODUCT_TREBLE_LINKER_NAMESPACES enforces usage of ld.config.vndk_lite.txt, which is less restrictive.
  • Not setting any of these props means ld.config.legacy.txt is used, which is meant for pre-treble devices and very permissive.

More on linker namespaces at

vintf - Vendor Interface Manifest

After creating the device manifest, defining DEVICE_MANIFEST_FILE and setting PRODUCT_ENFORCE_VINTF_MANIFEST_OVERRIDE := true as explained in vintf: develop for a new device. This will alert you if you a mismatch between HALs defined in the Device Manifest(DM) and those defined in your Device Compatibility Matrix.

You can also be alerted if you have defined HALs in your manifest but not told the framework about them in your Compatibility Matrix with VINTF_ENFORCE_NO_UNUSED_HALS := true. This invokes assemble_vintf in strict mode when checking at build time.

c.f. AssembleVintf.cpp(simplified here):

// If -c is provided, check it.
bool checkDualFile(const HalManifest& manifest, const CompatibilityMatrix& matrix) {
    if (getBooleanFlag("PRODUCT_ENFORCE_VINTF_MANIFEST")) {
        if (!manifest.checkCompatibility(matrix, &error)) {
            std::cerr << "Not compatible: " << error << std::endl;

    // Check HALs in device manifest that are not in framework matrix.
    if (getBooleanFlag("VINTF_ENFORCE_NO_UNUSED_HALS")) {
        auto unused = manifest.checkUnusedHals(matrix);
        if (!unused.empty()) {
            std::cerr << "Error: The following instances are in the device manifest but "
                      << "not specified in framework compatibility matrix: " << std::endl
                      << "    " << android::base::Join(unused, "\n    ") << std::endl

The other place PRODUCT_ENFORCE_VINTF_MANIFEST has an effect is with the Android HIDL service manager (configured via transport/Android.bp). If ENFORCE_VINTF_MANIFEST is defined, bool vintfLegacy = false; applies.

getRawServiceInternal(descriptor, instance, bool retry, bool getStub) {
    bool vintfLegacy = false;
    bool vintfLegacy = (transport == Transport::EMPTY);
    bool vintfHwbinder = (transport == Transport::HWBINDER);
    bool vintfPassthru = (transport == Transport::PASSTHROUGH);
    for (int tries = 0; !getStub && (vintfHwbinder || vintfLegacy); tries++) {
        if (getStub || vintfPassthru || vintfLegacy) {
            Return<sp<IBase>> ret = sm->get(descriptor, instance);
            waiter->done(); // exit loop
    if (getStub || vintfPassthru || vintfLegacy) {
        sp<IServiceManager> pm = getPassthroughServiceManager();

ENFORCE_VINTF_MANIFEST closes a loophole in getRawServiceInternal which means libhidltransport will no longer fetch services which are not hwbinder or passthrough, i.e. EMPTY transports will no longer be fetched.

To list all HALs and HAL services activity on-device, use the lshal command.

More on vintf at

Split SELinux policy

Pre-treble, the policy files for SElinux were situated on the ramdisk of the boot partition1. They consisted of an sepolicy binary and file_contexts files in the root directory ramdisk.

With PRODUCT_SEPOLICY_SPLIT_OVERRIDE set, the sepolicy binary will be split up into platform(system) and vendor policy files. They policy is also no longer a binary file but rather uses the new .cil plain-text format.

The platform .cil policy and context files will be pushed to /system/etc/selinux/, while the vendor .cil policy and context files will be pushed to /vendor/etc/selinux/.

Another change in behaviour when enabling the split is the effect of not_full_treble() macros: With SEPOLICY_SPLIT set, target_full_treble is set to true and the macros are now simply ignored, i.e. all the policy that you inclosed in not_full_treble(allow ...) statements is ignored.

There are also a lot of new neverallows introduced, so your policies might need updating. Finally, three violator` attributes are now banned:

  • binder_in_vendor_violators
  • socket_between_core_and_vendor_violators
  • vendor_executes_system_violators

More on implementing SELinux on

Compatible Properties

PRODUCT_COMPATIBLE_PROPERTY enables the whitelist for “compatible” properties, meaning vendor services only get access to vendor-namespaced properties (with some expections).

Manual override in


Enabled by default for SHIPPING_API_LEVEL >= 28.

Side effects:

  • service ril-daemon -> (see hardware/ril/rild/ rild.legacy.rc with service ril-daemon when non-compatible, rild.rc with service when using compatible)
  • prop rild.libpath -> vendor.rild.libpath (see rild/rild.c)
  • prop rild.libargs -> vendor.rild.libargs
  • compatible_property_only() and not_compatible_property() sepolicy macros

In init, only whitelisted properties are “actionable”, meaning they are allowed as triggers for e.g. on property:vendor.prop=1.

The whitelist - for init only - can be disabled at runtime by setting:


The whitelist resides in system/core/init/stable_properties.h and consists of a list of allowed prefixes and a list of allowed full property names.

These lists of properties are lifted straight from the `android-9.0.0_r34` branch and might not be complete, considering development for Android Q is in full swing. Please confirm the correctness yourself instead of relying on this article.

Allowed prefixes(kPartnerPrefixes):

  • init.svc.vendor.
  • ro.vendor.
  • persist.vendor.
  • vendor.
  • init.svc.odm.
  • ro.odm.
  • persist.odm.
  • odm.
  • ro.boot.

Allowed exported actionable properties(kExportedActionableProperties):

  • dev.bootcomplete
  • init.svc.console
  • init.svc.mediadrm
  • init.svc.surfaceflinger
  • init.svc.zygote
  • persist.bluetooth.btsnoopenable
  • persist.sys.crash_rcu
  • persist.sys.usb.usbradio.config
  • persist.sys.zram_enabled
  • ro.board.platform
  • ro.bootmode
  • ro.crypto.state
  • ro.crypto.type
  • ro.debuggable
  • sys.boot_completed
  • sys.boot_from_charger_mode
  • sys.retaildemo.enabled
  • sys.shutdown.requested
  • sys.usb.config
  • sys.usb.configfs
  • sys.usb.ffs.mtp.ready
  • sys.usb.ffs.ready
  • sys.user.0.ce_available
  • sys.vdso
  • vold.decrypt
  • vold.post_fs_data_done
  • vts.native_server.on
  • wlan.driver.status

  1. Exception: When using system-as-root, the sepolicy binary was not on the boot ramdisk but rather on the root of the system partition. For more, see Gotcha: sepolicy for system-as-root devices. [return]