Android getprop 属性限制详解:User 版本属性获取问题分析

Android getprop 属性限制详解:User 版本属性获取问题分析

文章目录

  • [Android getprop 属性限制详解:User 版本属性获取问题分析](#Android getprop 属性限制详解:User 版本属性获取问题分析)
    • 一、前言
    • [二、Android 系统属性机制概述](#二、Android 系统属性机制概述)
      • [2.1 系统属性简介](#2.1 系统属性简介)
      • [2.2 属性分类](#2.2 属性分类)
      • [2.3 属性服务架构](#2.3 属性服务架构)
    • 三、源码分析:属性访问控制机制
      • [3.1 属性服务源码位置](#3.1 属性服务源码位置)
      • [3.2 property_service.cpp 源码分析](#3.2 property_service.cpp 源码分析)
        • [3.2.1 属性服务核心功能](#3.2.1 属性服务核心功能)
        • [3.2.2 属性设置权限检查](#3.2.2 属性设置权限检查)
        • [3.2.3 重要发现:property_service.cpp 中无蓝牙特定限制](#3.2.3 重要发现:property_service.cpp 中无蓝牙特定限制)
        • [3.2.4 为什么有些属性在 User 版本无法获取?](#3.2.4 为什么有些属性在 User 版本无法获取?)
      • [3.3 构建类型与属性定义](#3.3 构建类型与属性定义)
        • [3.3.1 构建配置差异](#3.3.1 构建配置差异)
        • [3.3.2 条件编译属性](#3.3.2 条件编译属性)
      • [3.4 系统属性配置文件](#3.4 系统属性配置文件)
    • 四、蓝牙绝对音量属性详解
      • [4.1 正确的蓝牙绝对音量属性](#4.1 正确的蓝牙绝对音量属性)
      • [4.2 属性源码分析](#4.2 属性源码分析)
        • [4.2.1 AudioService 中的使用](#4.2.1 AudioService 中的使用)
        • [4.2.2 AvrcpVolumeManager 中的使用](#4.2.2 AvrcpVolumeManager 中的使用)
        • [4.2.3 开发者选项中的设置](#4.2.3 开发者选项中的设置)
      • [4.3 属性特点分析](#4.3 属性特点分析)
        • [4.3.1 persist 前缀的含义](#4.3.1 persist 前缀的含义)
        • [4.3.2 与 debug 前缀的区别](#4.3.2 与 debug 前缀的区别)
      • [4.4 User 版本中的真实行为(重要)](#4.4 User 版本中的真实行为(重要))
        • [4.4.1 Android 15 User 版本实测结果](#4.4.1 Android 15 User 版本实测结果)
        • [4.4.2 原因分析:SELinux 读取权限限制](#4.4.2 原因分析:SELinux 读取权限限制)
          • [SELinux 属性读取机制](#SELinux 属性读取机制)
          • [property_contexts 中的属性类型定义](#property_contexts 中的属性类型定义)
          • [shell 域的权限定义](#shell 域的权限定义)
        • [4.4.3 验证 SELinux 拒绝日志](#4.4.3 验证 SELinux 拒绝日志)
        • [4.4.4 属性读取权限的层级关系](#4.4.4 属性读取权限的层级关系)
        • [4.4.5 不同域对蓝牙属性的访问权限](#4.4.5 不同域对蓝牙属性的访问权限)
        • [4.4.6 为什么 system_server 可以读取?](#4.4.6 为什么 system_server 可以读取?)
      • [4.5 persist.bluetooth.disableabsvol 的实际归属](#4.5 persist.bluetooth.disableabsvol 的实际归属)
        • [4.5.1 属性匹配分析](#4.5.1 属性匹配分析)
        • [4.5.2 为什么 shell 无法读取](#4.5.2 为什么 shell 无法读取)
        • [4.5.3 与 a2dp_offload 属性的对比](#4.5.3 与 a2dp_offload 属性的对比)
      • [4.6 正确的属性获取方式](#4.6 正确的属性获取方式)
        • [4.6.1 应用开发者](#4.6.1 应用开发者)
        • [4.6.2 系统开发者](#4.6.2 系统开发者)
        • [4.6.3 厂商定制方案](#4.6.3 厂商定制方案)
      • [4.5 其他蓝牙相关属性](#4.5 其他蓝牙相关属性)
    • [五、User 版本无法获取的属性详解](#五、User 版本无法获取的属性详解)
      • [5.1 常见受限属性列表](#5.1 常见受限属性列表)
      • [5.2 详细分类说明](#5.2 详细分类说明)
        • [5.2.1 蓝牙相关属性(bluetooth_prop)](#5.2.1 蓝牙相关属性(bluetooth_prop))
        • [5.2.2 NFC 相关属性(nfc_prop)](#5.2.2 NFC 相关属性(nfc_prop))
        • [5.2.3 音频相关属性(audio_prop)](#5.2.3 音频相关属性(audio_prop))
        • [5.2.4 相机相关属性(camera_prop)](#5.2.4 相机相关属性(camera_prop))
        • [5.2.5 调试属性(debug_prop)](#5.2.5 调试属性(debug_prop))
      • [5.3 验证属性是否受限的方法](#5.3 验证属性是否受限的方法)
      • [5.4 属性限制流程图](#5.4 属性限制流程图)
    • 六、自定义受限属性设计与实现
      • [6.1 需求场景](#6.1 需求场景)
      • [6.2 实现步骤](#6.2 实现步骤)
      • [6.3 完整示例](#6.3 完整示例)
      • [6.4 验证实现](#6.4 验证实现)
      • [6.5 使用自定义属性](#6.5 使用自定义属性)
        • [Java 代码(需要系统权限)](#Java 代码(需要系统权限))
        • [Native 代码](#Native 代码)
      • [6.6 设计原则](#6.6 设计原则)
    • 六、源码深入分析
      • [5.1 init 进程中的属性服务](#5.1 init 进程中的属性服务)
        • [5.1.1 属性服务启动流程](#5.1.1 属性服务启动流程)
        • [5.1.2 属性服务实现](#5.1.2 属性服务实现)
      • [5.2 属性可获取性检查](#5.2 属性可获取性检查)
      • [5.3 Bionic 层属性获取](#5.3 Bionic 层属性获取)
      • [5.4 Java 层属性获取](#5.4 Java 层属性获取)
    • [七、SELinux 权限控制详解(核心机制)详解](#七、SELinux 权限控制详解(核心机制)详解)
      • [7.1 属性读取权限的真实机制](#7.1 属性读取权限的真实机制)
        • [7.1.1 属性读取的 SELinux 检查点](#7.1.1 属性读取的 SELinux 检查点)
        • [7.1.2 属性文件的 SELinux 标签](#7.1.2 属性文件的 SELinux 标签)
      • [7.2 property_contexts 文件详解](#7.2 property_contexts 文件详解)
        • [7.2.1 文件位置](#7.2.1 文件位置)
        • [7.2.2 property_contexts 语法格式](#7.2.2 property_contexts 语法格式)
        • [7.2.3 实际配置示例解析](#7.2.3 实际配置示例解析)
        • [7.2.4 类型注解详解(Android 10+)](#7.2.4 类型注解详解(Android 10+))
        • [7.2.5 属性匹配优先级规则](#7.2.5 属性匹配优先级规则)
        • [7.2.6 属性作用域与访问权限](#7.2.6 属性作用域与访问权限)
        • [7.2.7 查看设备上的实际配置](#7.2.7 查看设备上的实际配置)
      • [7.3 如何验证 shell 域没有 bluetooth_prop 权限](#7.3 如何验证 shell 域没有 bluetooth_prop 权限)
        • [7.3.1 SELinux 权限机制说明](#7.3.1 SELinux 权限机制说明)
        • [7.3.2 查看 shell.te 源码](#7.3.2 查看 shell.te 源码)
        • [7.3.3 在设备上验证方法](#7.3.3 在设备上验证方法)
        • [7.3.4 查看哪些域有 bluetooth_prop 权限](#7.3.4 查看哪些域有 bluetooth_prop 权限)
        • [7.3.5 bluetooth.te 中的权限定义](#7.3.5 bluetooth.te 中的权限定义)
        • [7.3.6 system_server.te 中的权限定义](#7.3.6 system_server.te 中的权限定义)
      • [7.4 SELinux neverallow 规则(禁止性规则)](#7.4 SELinux neverallow 规则(禁止性规则))
      • [7.5 属性权限完整对照表](#7.5 属性权限完整对照表)
      • [7.6 SELinux 编译宏详解](#7.6 SELinux 编译宏详解)
      • [7.7 属性权限层级图](#7.7 属性权限层级图)
      • [6.1 SELinux 属性上下文](#6.1 SELinux 属性上下文)
      • [6.2 SELinux 权限规则](#6.2 SELinux 权限规则)
      • [6.3 域权限配置](#6.3 域权限配置)
      • [6.4 SELinux 策略编译宏](#6.4 SELinux 策略编译宏)
    • 八、构建类型差异对比
      • [8.1 构建类型说明](#8.1 构建类型说明)
      • [8.2 关键差异:userdebug vs user 的 SELinux 策略](#8.2 关键差异:userdebug vs user 的 SELinux 策略)
        • [8.2.1 SELinux 策略的条件编译](#8.2.1 SELinux 策略的条件编译)
        • [8.2.2 userdebug_or_eng 宏定义](#8.2.2 userdebug_or_eng 宏定义)
        • [8.2.3 构建类型与策略编译的关系](#8.2.3 构建类型与策略编译的关系)
      • [8.3 实际验证方法](#8.3 实际验证方法)
        • [8.3.1 查看 SELinux 策略差异](#8.3.1 查看 SELinux 策略差异)
        • [8.3.2 检查构建类型](#8.3.2 检查构建类型)
        • [8.3.3 查看 SELinux 策略源码](#8.3.3 查看 SELinux 策略源码)
      • [8.4 属性访问权限对比](#8.4 属性访问权限对比)
      • [8.5 构建配置文件](#8.5 构建配置文件)
      • [8.6 为什么要有这个差异?](#8.6 为什么要有这个差异?)
      • [8.7 userdebug_or_eng 宏的定义位置](#8.7 userdebug_or_eng 宏的定义位置)
        • [8.7.1 宏定义来源](#8.7.1 宏定义来源)
        • [8.7.2 宏定义的真实注入流程](#8.7.2 宏定义的真实注入流程)
        • [8.7.3 如何查找宏的实际定义位置](#8.7.3 如何查找宏的实际定义位置)
        • [8.7.4 Android 12+ 的变化(Soong 构建系统)](#8.7.4 Android 12+ 的变化(Soong 构建系统))
        • [8.7.5 te_macros 文件中的典型定义(旧版本参考)](#8.7.5 te_macros 文件中的典型定义(旧版本参考))
        • [8.7.3 m4 宏处理示例](#8.7.3 m4 宏处理示例)
        • [8.7.4 构建系统中的相关文件](#8.7.4 构建系统中的相关文件)
        • [8.7.5 查看 te_macros 中的相关宏](#8.7.5 查看 te_macros 中的相关宏)
        • [8.7.6 自定义条件宏](#8.7.6 自定义条件宏)
        • [8.7.7 验证构建类型](#8.7.7 验证构建类型)
        • [8.7.8 完整的构建类型与权限关系图](#8.7.8 完整的构建类型与权限关系图)
      • [7.1 构建类型说明](#7.1 构建类型说明)
      • [8.2 属性访问权限对比](#8.2 属性访问权限对比)
      • [7.3 构建配置文件](#7.3 构建配置文件)
    • 九、解决方案
      • [9.1 方案一:使用开发者选项设置](#9.1 方案一:使用开发者选项设置)
      • [9.2 方案二:使用 Settings 系统](#9.2 方案二:使用 Settings 系统)
      • [9.3 方案三:定义自定义系统属性](#9.3 方案三:定义自定义系统属性)
        • [步骤 1:定义属性](#步骤 1:定义属性)
        • [步骤 2:添加 SELinux 上下文](#步骤 2:添加 SELinux 上下文)
        • [步骤 3:添加 SELinux 权限](#步骤 3:添加 SELinux 权限)
        • [步骤 4:修改源码使用自定义属性](#步骤 4:修改源码使用自定义属性)
      • [9.4 方案四:修改系统源码(仅限系统定制)](#9.4 方案四:修改系统源码(仅限系统定制))
      • [9.5 方案五:使用 DeviceConfig](#9.5 方案五:使用 DeviceConfig)
    • 十、实际案例分析
    • 十一、最佳实践建议
      • [10.1 属性使用原则](#10.1 属性使用原则)
      • [10.2 代码兼容性处理](#10.2 代码兼容性处理)
      • [10.3 调试与验证](#10.3 调试与验证)
    • 十三、总结
    • 参考资料
    • 附录:常见问题解答
      • [Q1: 为什么我在 system/sepolicy/Android.mk 中找不到 userdebug_or_eng 宏的定义?](#Q1: 为什么我在 system/sepolicy/Android.mk 中找不到 userdebug_or_eng 宏的定义?)
      • [Q2: 为什么 userdebug 版本能读取 bluetooth_prop,而 user 版本不能?](#Q2: 为什么 userdebug 版本能读取 bluetooth_prop,而 user 版本不能?)
      • [Q3: 如何让 user 版本的 shell 也能读取 bluetooth_prop?](#Q3: 如何让 user 版本的 shell 也能读取 bluetooth_prop?)

一、前言

在 Android 系统开发过程中,getprop 是一个非常重要的命令行工具,用于获取系统属性值。

然而,在实际开发中,我们经常会遇到这样一个问题:

某些系统属性在 eng(工程)版本或 userdebug 版本可以正常获取,但在 user(用户)版本却无法获取到,返回空值或默认值。

典型的例子包括蓝牙绝对音量相关属性、调试开关属性等。

本文将从 Android 系统源码层面深入分析这一现象的原因,并提供相应的解决方案。

最近调试发现:

蓝牙绝对音量属性 persist.bluetooth.disableabsvol 在user版本通过adb 窗口获取不到,debug版本可以。

这个属性看起来不是什么特殊限制的属性,都是就是有这样的问题。应该是源码限制导致。

你只有明白有些 persist.XXX 属性也有可能在user版本获取不到就行了。

下面分析过程可看可不看。因为不保证正确率,仅供参考。


二、Android 系统属性机制概述

2.1 系统属性简介

Android 系统属性(System Properties)是一组全局可访问的键值对,用于存储系统配置、状态信息等。这些属性在整个系统中共享,可以通过以下方式访问:

bash 复制代码
# Shell 命令获取属性
adb shell getprop ro.build.version.sdk

# Shell 命令设置属性(需要权限)
adb shell setprop debug.bluetooth.absolute_volume 1
java 复制代码
// Java 代码获取属性
String value = SystemProperties.get("ro.build.version.sdk");

// Java 代码设置属性
SystemProperties.set("debug.bluetooth.absolute_volume", "1");

2.2 属性分类

Android 系统属性按前缀可分为以下几类:

前缀 说明 可写性 示例
ro. 只读属性,系统启动后不可修改 只读 ro.build.version.sdk
sys. 系统属性,可读写 可写 sys.usb.config
persist. 持久化属性,重启后保留 可写 persist.sys.locale
debug. 调试属性,部分在 user 版本受限 可写 debug.sf.nobootanimation
log. 日志相关属性 可写 log.tag.Bluetooth

2.3 属性服务架构

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    属性服务架构图                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐     ┌─────────────┐     ┌─────────────┐  │
│  │   App 层    │     │  Native 层  │     │  Shell 层   │  │
│  │ SystemProps │     │ __system_property_get │  getprop   │  │
│  └──────┬──────┘     └──────┬──────┘     └──────┬──────┘  │
│         │                   │                   │          │
│         └───────────────────┼───────────────────┘          │
│                             │                              │
│                             ▼                              │
│                    ┌─────────────────┐                     │
│                    │   Property Service   │                │
│                    │   (init 进程)         │                │
│                    └─────────────────┘                     │
│                             │                              │
│                             ▼                              │
│                    ┌─────────────────┐                     │
│                    │   /dev/__properties__ │               │
│                    │   (共享内存映射)       │               │
│                    └─────────────────┘                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

三、源码分析:属性访问控制机制

3.1 属性服务源码位置

Android 系统属性服务的核心代码位于以下目录:

复制代码
system/core/init/property_service.cpp    # 属性服务核心实现
system/core/init/properties.h            # 属性定义头文件
bionic/libc/bionic/system_property_api.cpp  # Bionic 属性 API
frameworks/base/core/java/android/os/SystemProperties.java  # Java 层接口

3.2 property_service.cpp 源码分析

根据 AOSP 源码分析,property_service.cpp 文件主要负责属性的设置权限控制,而非属性获取的过滤。属性的限制机制主要来自于 SELinux 策略和构建配置。

3.2.1 属性服务核心功能
cpp 复制代码
// system/core/init/property_service.cpp

// 属性服务的核心功能包括:
// 1. 属性的初始化和加载
// 2. 属性设置请求的处理
// 3. 属性权限的验证(设置权限,非读取权限)

// 属性设置权限白名单(旧版本)
// 注意:Android 8.0+ 后,权限控制主要通过 SELinux 实现
struct {
    const char *prefix;
    unsigned int uid;
    unsigned int gid;
} property_perms[] = {
    { "net.",          AID_RADIO,    0 },
    { "sys.",          AID_SYSTEM,   0 },
    { "persist.sys.",  AID_SYSTEM,   0 },
    { "persist.service.", AID_SYSTEM, 0 },
    // ... 其他权限定义
};
3.2.2 属性设置权限检查
cpp 复制代码
// system/core/init/property_service.cpp

// 检查属性设置权限
static Result<void> CheckPermissions(const std::string& name, 
                                      const std::string& value,
                                      const std::string& source_context,
                                      const ucred* cred) {
    // 1. 检查属性名是否合法
    if (!IsLegalPropertyName(name)) {
        return Error() << "Illegal property name";
    }
    
    // 2. 检查 SELinux 权限(主要权限控制)
    if (!CheckSelinuxPermission(name, value, source_context, cred)) {
        return Error() << "SELinux permission denied";
    }
    
    // 3. 检查属性设置权限(uid/gid)
    // ...
    
    return {};
}
3.2.3 重要发现:property_service.cpp 中无蓝牙特定限制

经过源码分析,property_service.cpp 文件中:

  1. 没有蓝牙属性的特定限制代码:蓝牙属性的限制不是在此文件中硬编码的

  2. 属性读取无过滤:属性获取(getprop)操作不经过权限过滤,直接从共享内存读取

  3. 限制来源于 SELinux:真正的访问控制由 SELinux 策略实现

    ┌─────────────────────────────────────────────────────────────┐
    │ 属性访问控制真实机制 │
    ├─────────────────────────────────────────────────────────────┤
    │ │
    │ 属性读取 (getprop): │
    │ ┌─────────────┐ │
    │ │ getprop │ │
    │ │ SystemProperties.get() │ │
    │ └──────┬──────┘ │
    │ │ │
    │ ▼ │
    │ ┌─────────────────────────────┐ │
    │ │ 直接从共享内存读取 │ │
    │ │ /dev/properties │ │
    │ │ (无权限过滤) │ │
    │ └─────────────────────────────┘ │
    │ │
    │ 属性设置 (setprop): │
    │ ┌─────────────┐ │
    │ │ setprop │ │
    │ │ SystemProperties.set() │ │
    │ └──────┬──────┘ │
    │ │ │
    │ ▼ │
    │ ┌─────────────────────────────┐ │
    │ │ Property Service (init) │ │
    │ │ 1. 检查 SELinux 权限 │ │
    │ │ 2. 检查 uid/gid 权限 │ │
    │ │ 3. 写入属性 │ │
    │ └─────────────────────────────┘ │
    │ │
    └─────────────────────────────────────────────────────────────┘

3.2.4 为什么有些属性在 User 版本无法获取?

经过深入分析,属性在 User 版本"无法获取"的原因主要有以下几种:

原因 说明 示例
属性根本不存在 属性未被定义或未被设置 部分动态属性
SELinux 读取限制 SELinux 策略禁止某些域读取特定属性 debug.* 类属性
属性来源未加载 属性定义文件未被加载到 user 版本 部分调试配置
构建时未包含 某些属性只在 debug 构建中定义 ro.debuggable=1

3.3 构建类型与属性定义

3.3.1 构建配置差异
makefile 复制代码
# build/core/main.mk

# 根据构建类型设置不同的属性
ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
  # Debug 构建属性
  ADDITIONAL_DEFAULT_PROPERTIES += \
      ro.debuggable=1 \
      ro.adb.secure=0
else
  # User 构建属性
  ADDITIONAL_DEFAULT_PROPERTIES += \
      ro.debuggable=0 \
      ro.adb.secure=1
endif
3.3.2 条件编译属性

某些属性只在特定构建类型中定义:

makefile 复制代码
# 某些系统属性文件中的条件定义
ifneq ($(TARGET_BUILD_VARIANT),user)
  # 非 User 版本才定义的调试属性
  PRODUCT_PROPERTY_OVERRIDES += \
      debug.sf.nobootanimation=1
endif

3.4 系统属性配置文件

Android 系统通过 property_contexts 文件定义属性的 SELinux 上下文和权限:

plaintext 复制代码
# system/sepolicy/private/property_contexts

# 调试属性 - user 版本受限
debug.                  u:object_r:debug_prop:s0
debug.bluetooth.        u:object_r:bluetooth_debug_prop:s0

# 蓝牙属性
bluetooth.              u:object_r:bluetooth_prop:s0
ro.bluetooth.           u:object_r:bluetooth_prop:s0

# 系统属性
sys.                    u:object_r:system_prop:s0
persist.sys.            u:object_r:persist_system_prop:s0

四、蓝牙绝对音量属性详解

4.1 正确的蓝牙绝对音量属性

重要更正 :蓝牙绝对音量的系统属性是 persist.bluetooth.disableabsvol,而非 debug.bluetooth.absolute_volume

属性名称 说明 默认值
persist.bluetooth.disableabsvol 禁用蓝牙绝对音量 false(空或不存在)
bash 复制代码
# 禁用蓝牙绝对音量(设置该属性为 true)
adb shell setprop persist.bluetooth.disableabsvol true

# 查看当前状态
adb shell getprop persist.bluetooth.disableabsvol

# 恢复默认(启用绝对音量)
adb shell setprop persist.bluetooth.disableabsvol false
# 或者删除属性(重启后生效)

4.2 属性源码分析

4.2.1 AudioService 中的使用
java 复制代码
// frameworks/base/services/core/java/com/android/server/audio/AudioService.java

/**
 * 检查是否应该禁用 AVRCP 绝对音量
 * @return true 表示禁用绝对音量
 */
private boolean shouldEnableAvrcpAbsoluteVolume() {
    // 读取 persist.bluetooth.disableabsvol 属性
    // 注意:属性名为 "disableabsvol",表示禁用绝对音量
    // 属性值为 true 时禁用,false 或不存在时启用
    return !SystemProperties.getBoolean("persist.bluetooth.disableabsvol", false);
}

// 或者更明确的写法
private boolean isAvrcpAbsoluteVolumeEnabled() {
    // 默认启用绝对音量(返回 false 表示不禁用)
    return !SystemProperties.getBoolean("persist.bluetooth.disableabsvol", false);
}
4.2.2 AvrcpVolumeManager 中的使用
java 复制代码
// packages/apps/Bluetooth/src/com/android/bluetooth/avrcp/AvrcpVolumeManager.java

/**
 * 检查是否禁用绝对音量
 */
private boolean isAbsoluteVolumeDisabled() {
    // 读取系统属性
    return SystemProperties.getBoolean("persist.bluetooth.disableabsvol", false);
}

/**
 * 初始化时检查绝对音量设置
 */
public void init() {
    if (isAbsoluteVolumeDisabled()) {
        Log.w(TAG, "Absolute volume is disabled by system property");
        // 使用相对音量模式
    } else {
        Log.d(TAG, "Absolute volume is enabled");
        // 使用绝对音量模式
    }
}
4.2.3 开发者选项中的设置
java 复制代码
// packages/apps/Settings/src/com/android/settings/development/DevelopmentSettings.java

// 开发者选项中的 "禁用绝对音量" 开关
private static final String KEY_DISABLE_BLUETOOTH_ABSOLUTE_VOLUME = 
    "disable_bluetooth_absolute_volume";

// 蓝牙绝对音量属性名称
private static final String BLUETOOTH_DISABLE_ABS_VOL_PROPERTY = 
    "persist.bluetooth.disableabsvol";

@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
    if (preference.getKey().equals(KEY_DISABLE_BLUETOOTH_ABSOLUTE_VOLUME)) {
        boolean disable = (Boolean) newValue;
        // 写入系统属性
        SystemProperties.set(BLUETOOTH_DISABLE_ABS_VOL_PROPERTY, 
                            disable ? "true" : "false");
        // 提示用户需要重启生效
        if (disable) {
            showToast(R.string.bluetooth_absolute_volume_disabled);
        }
        return true;
    }
    return false;
}

4.3 属性特点分析

4.3.1 persist 前缀的含义

persist.bluetooth.disableabsvol 使用 persist. 前缀,具有以下特点:

特性 说明
持久化存储 属性值保存在 /data/property/ 目录下
重启后保留 设备重启后属性值不会丢失
可读写 所有构建类型都可以读取和设置
不受构建类型限制 User 版本也可以正常访问
4.3.2 与 debug 前缀的区别
对比项 persist.* debug.*
存储位置 /data/property/persistent_properties 仅内存中
重启后 ✅ 保留 ❌ 丢失
User 版本可访问 ✅ 是 ⚠️ 受限
设置权限 需要系统权限 需要 root 或 shell
SELinux 限制 较宽松 较严格

4.4 User 版本中的真实行为(重要)

4.4.1 Android 15 User 版本实测结果

根据 Android 15 User 版本的实际测试,persist.bluetooth.disableabsvol 属性在 adb shell 中无法获取

bash 复制代码
# Android 15 User 版本测试
$ adb shell getprop persist.bluetooth.disableabsvol
# 返回空值,即使属性已被设置

# 尝试查看所有蓝牙相关属性
$ adb shell getprop | grep bluetooth
# 可能也看不到 persist.bluetooth.disableabsvol
4.4.2 原因分析:SELinux 读取权限限制

核心发现 :在 Android 14/15 中,SELinux 策略对属性的读取权限 进行了严格限制,即使是 persist. 前缀的属性。

SELinux 属性读取机制
复制代码
┌─────────────────────────────────────────────────────────────┐
│              Android 14/15 属性读取权限机制                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  传统的理解(错误):                                         │
│  ┌─────────────┐                                            │
│  │ getprop     │                                            │
│  └──────┬──────┘                                            │
│         │ 直接读取共享内存                                   │
│         ▼                                                   │
│  ┌─────────────────────────────┐                           │
│  │ /dev/__properties__         │                           │
│  │ 无权限检查                  │  ← 实际上有权限检查!      │
│  └─────────────────────────────┘                           │
│                                                             │
│  实际机制(正确):                                           │
│  ┌─────────────┐                                            │
│  │ getprop     │                                            │
│  └──────┬──────┘                                            │
│         │                                                   │
│         ▼                                                   │
│  ┌─────────────────────────────┐                           │
│  │ __system_property_find      │                           │
│  │ 检查 SELinux 上下文          │                           │
│  └──────────────┬──────────────┘                           │
│                 │                                           │
│                 ▼                                           │
│  ┌─────────────────────────────┐                           │
│  │ SELinux Policy 检查         │                           │
│  │ shell 域是否有权限读取       │                           │
│  │ 该属性类型?                 │                           │
│  └──────────────┬──────────────┘                           │
│                 │                                           │
│        ┌────────┴────────┐                                  │
│        │                 │                                  │
│        ▼                 ▼                                  │
│  ┌──────────┐     ┌──────────────┐                         │
│  │ 允许读取 │     │ 返回空值     │                         │
│  │ 返回属性值│     │ 或访问被拒绝 │                         │
│  └──────────┘     └──────────────┘                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘
property_contexts 中的属性类型定义
plaintext 复制代码
# system/sepolicy/private/property_contexts

# 蓝牙相关属性的类型定义
persist.bluetooth.    u:object_r:bluetooth_prop:s0
ro.bluetooth.         u:object_r:bluetooth_prop:s0
bluetooth.            u:object_r:bluetooth_prop:s0
shell 域的权限定义
sepolicy 复制代码
# system/sepolicy/public/shell.te

# shell 域可以读取的属性类型
# 注意:bluetooth_prop 可能不在允许列表中!

# shell 域允许读取的属性示例
get_prop(shell, default_prop)
get_prop(shell, log_prop)
get_prop(shell, shell_prop)
get_prop(shell, system_prop)

# 如果 bluetooth_prop 没有被明确允许,shell 就无法读取
# 即使是 persist.bluetooth.* 属性也无法获取
4.4.3 验证 SELinux 拒绝日志

可以通过以下方式验证 SELinux 是否拒绝了属性读取:

bash 复制代码
# 查看 SELinux 拒绝日志
$ adb shell dmesg | grep -i "avc.*bluetooth.*prop"
# 或
$ adb shell "logcat -b all | grep -i 'avc denied.*property'"

# 示例输出
# avc: denied { read } for name="persist.bluetooth.disableabsvol" 
# scontext=u:r:shell:s0 tcontext=u:object_r:bluetooth_prop:s0 
# tclass=file permissive=0
4.4.4 属性读取权限的层级关系
复制代码
┌─────────────────────────────────────────────────────────────┐
│              属性读取权限检查流程                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 应用/进程尝试读取属性                                    │
│     ↓                                                       │
│  2. 系统获取进程的 SELinux 域 (scontext)                     │
│     例如:shell 域、untrusted_app 域、system_server 域       │
│     ↓                                                       │
│  3. 系统查找属性的安全上下文 (tcontext)                      │
│     例如:bluetooth_prop、system_prop、debug_prop            │
│     ↓                                                       │
│  4. SELinux 策略检查                                        │
│     - 该域是否有 get_prop 权限读取该类型属性?               │
│     - 是否存在 neverallow 规则禁止?                         │
│     ↓                                                       │
│  5. 结果                                                    │
│     - 允许:返回属性值                                       │
│     - 拒绝:返回空值,记录 avc denied 日志                   │
│                                                             │
└─────────────────────────────────────────────────────────────┘
4.4.5 不同域对蓝牙属性的访问权限
域 (Domain) persist.bluetooth.* 访问权限 说明
shell ❌ 受限 adb shell 无法读取
untrusted_app ❌ 受限 普通应用无法读取
system_server ✅ 可读取 系统服务可读取
bluetooth ✅ 可读取 蓝牙服务可读取
radio ⚠️ 可能受限 电信服务受限
vendor_* ⚠️ 取决于策略 厂商定制域
4.4.6 为什么 system_server 可以读取?
sepolicy 复制代码
# system/sepolicy/private/system_server.te

# system_server 域被明确授予读取 bluetooth_prop 的权限
get_prop(system_server, bluetooth_prop)

# 或者更广泛的权限
get_prop(system_server, system_prop)

4.5 persist.bluetooth.disableabsvol 的实际归属

4.5.1 属性匹配分析

根据 property_contexts 的配置:

plaintext 复制代码
persist.bluetooth.      u:object_r:bluetooth_prop:s0

persist.bluetooth.disableabsvol 会匹配这个前缀规则,因此:

属性 匹配规则 SELinux 类型
persist.bluetooth.disableabsvol persist.bluetooth. 前缀 bluetooth_prop
4.5.2 为什么 shell 无法读取
sepolicy 复制代码
# shell 域没有 bluetooth_prop 的读取权限
# system/sepolicy/public/shell.te 中没有:
# get_prop(shell, bluetooth_prop)

# 只有以下域可以读取:
# - bluetooth 域(蓝牙服务)
# - system_server 域(系统服务)
# - init 域(初始化进程)
4.5.3 与 a2dp_offload 属性的对比
plaintext 复制代码
# 这些属性使用不同的 SELinux 类型
persist.bluetooth.a2dp_offload.cap      u:object_r:bluetooth_a2dp_offload_prop:s0 exact string
persist.bluetooth.a2dp_offload.disabled u:object_r:bluetooth_a2dp_offload_prop:s0 exact bool

这些属性使用 bluetooth_a2dp_offload_prop 类型,可能有不同的权限配置。

4.6 正确的属性获取方式

4.6.1 应用开发者

对于普通应用开发者,无法直接读取 persist.bluetooth.disableabsvol,需要通过系统 API 获取:

java 复制代码
/**
 * 应用层获取蓝牙绝对音量状态的正确方式
 */
public class BluetoothVolumeHelper {
    
    /**
     * 方法一:通过 AudioManager 检查(如果 API 支持)
     */
    public static boolean isAbsoluteVolumeEnabled(Context context) {
        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        // Android 11+ 可能提供相关 API
        // 具体取决于系统实现
        return true; // 默认启用
    }
    
    /**
     * 方法二:监听蓝牙设备连接状态变化
     * 通过 BluetoothA2dp 的回调判断绝对音量是否生效
     */
    public static void monitorBluetoothVolume(Context context, 
                                               BluetoothA2dp a2dp,
                                               BluetoothDevice device) {
        // 监听 A2DP 连接状态
        // 当设备连接时,观察音量同步行为
    }
    
    /**
     * 方法三:通过 Settings(如果厂商实现)
     */
    public static boolean checkDeveloperOption(Context context) {
        // 某些厂商可能将此选项暴露到 Settings
        return Settings.Global.getInt(
            context.getContentResolver(),
            "bluetooth_disable_absolute_volume",
            0
        ) == 1;
    }
}
4.6.2 系统开发者

对于有系统权限的应用或系统服务:

java 复制代码
/**
 * 系统应用读取蓝牙绝对音量属性
 */
public class SystemBluetoothVolumeHelper {
    
    /**
     * 直接读取系统属性(需要系统权限)
     */
    public static boolean isAbsoluteVolumeDisabled() {
        // 需要在 system_server 或 system_app 域运行
        return SystemProperties.getBoolean("persist.bluetooth.disableabsvol", false);
    }
    
    /**
     * 通过 BluetoothProperties API(Android 10+)
     */
    public static boolean isAbsoluteVolumeDisabledNewApi() {
        // Android 10+ 提供的类型安全属性 API
        // BluetoothProperties.disable_abs_vol() 等
        // 具体方法名取决于系统版本
        return false;
    }
}
4.6.3 厂商定制方案

如果需要让 shell 或普通应用能读取该属性,需要修改 SELinux 策略:

sepolicy 复制代码
# device/<厂商>/<设备>/sepolicy/shell.te

# 允许 shell 域读取 bluetooth_prop
get_prop(shell, bluetooth_prop)

或修改 property_contexts 将属性映射到更宽松的类型:

plaintext 复制代码
# device/<厂商>/<设备>/sepolicy/property_contexts

# 将特定属性映射到 system_prop(shell 默认可读)
persist.bluetooth.disableabsvol    u:object_r:system_prop:s0

4.5 其他蓝牙相关属性

属性名称 说明 User 版本可访问
persist.bluetooth.disableabsvol 禁用绝对音量 ✅ 可读取
ro.bluetooth.hfp.conn HFP 连接状态 ✅ 可读取
ro.bluetooth.a2dp.conn A2DP 连接状态 ✅ 可读取
persist.bluetooth.adapter.address 蓝牙地址 ✅ 可读取
persist.bluetooth.sco.available SCO 可用状态 ✅ 可读取
debug.bluetooth.* 蓝牙调试属性 ⚠️ 受限

五、User 版本无法获取的属性详解

5.1 常见受限属性列表

以下是在 User 版本 adb shell 无法获取的常见属性:

属性名称 SELinux 类型 说明 受限原因
persist.bluetooth.disableabsvol bluetooth_prop 蓝牙绝对音量开关 shell 域无 bluetooth_prop 权限
persist.bluetooth.btsnoopdefaultmode bluetooth_prop 蓝牙日志默认模式 shell 域无 bluetooth_prop 权限
persist.bluetooth.snoop_log_mode bluetooth_prop 蓝牙日志模式 shell 域无 bluetooth_prop 权限
persist.bluetooth.a2dp_offload.cap bluetooth_a2dp_offload_prop A2DP 卸载能力 shell 域无相关权限
persist.bluetooth.a2dp_offload.disabled bluetooth_a2dp_offload_prop A2DP 卸载禁用 shell 域无相关权限
persist.nfc.* nfc_prop NFC 配置 shell 域无 nfc_prop 权限
persist.audio.* audio_prop 音频配置 shell 域无 audio_prop 权限
persist.camera.* camera_prop 相机配置 shell 域无 camera_prop 权限
debug.* debug_prop 调试属性 User 版本受限
ro.debuggable debug_prop 调试标志 User 版本固定为 0
ro.adb.secure shell_prop ADB 安全 User 版本固定为 1
ro.build.selinux selinux_prop SELinux 状态 部分受限
sys.oem_unlock_allowed oem_lock_prop OEM 解锁状态 敏感安全属性
persist.sys.usb.config system_prop USB 配置 可读但写入受限

5.2 详细分类说明

5.2.1 蓝牙相关属性(bluetooth_prop)
bash 复制代码
# User 版本无法获取的蓝牙属性
$ adb shell getprop persist.bluetooth.disableabsvol
# 返回空值

$ adb shell getprop persist.bluetooth.btsnoopdefaultmode
# 返回空值

$ adb shell getprop persist.bluetooth.snoop_log_mode
# 返回空值

原因persist.bluetooth.* 映射到 bluetooth_prop 类型,shell 域没有读取权限。

5.2.2 NFC 相关属性(nfc_prop)
bash 复制代码
# User 版本无法获取的 NFC 属性
$ adb shell getprop persist.nfc.fw_download_enabled
# 返回空值

$ adb shell getprop persist.nfc.enabled
# 返回空值(部分设备)
5.2.3 音频相关属性(audio_prop)
bash 复制代码
# User 版本可能受限的音频属性
$ adb shell getprop persist.audio.fluence.voicecall
# 返回空值(部分设备)

$ adb shell getprop persist.audio.fluence.speaker
# 返回空值(部分设备)
5.2.4 相机相关属性(camera_prop)
bash 复制代码
# User 版本无法获取的相机属性
$ adb shell getprop persist.camera.hal.debug
# 返回空值

$ adb shell getprop persist.camera.stats.debug
# 返回空值
5.2.5 调试属性(debug_prop)
bash 复制代码
# User 版本无法获取的调试属性
$ adb shell getprop debug.sf.nobootanimation
# 返回空值

$ adb shell getprop debug.sf.latch_unsignaled
# 返回空值

$ adb shell getprop debug.hwui.renderer
# 返回空值

5.3 验证属性是否受限的方法

bash 复制代码
# 方法一:直接获取属性
$ adb shell getprop <属性名>
# 返回空值可能是受限

# 方法二:查看 SELinux 拒绝日志
$ adb shell "dmesg | grep -i 'avc.*<属性名>'"

# 方法三:查看属性的安全上下文
$ adb shell "grep '<属性前缀>' /system/etc/selinux/plat_property_contexts"
$ adb shell "grep '<属性前缀>' /vendor/etc/selinux/vendor_property_contexts"

# 方法四:临时关闭 SELinux 测试
$ adb root
$ adb shell setenforce 0
$ adb shell getprop <属性名>
# 如果此时能获取,说明确实是 SELinux 限制

5.4 属性限制流程图

复制代码
┌─────────────────────────────────────────────────────────────┐
│           属性访问请求处理流程                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  adb shell getprop persist.bluetooth.disableabsvol          │
│         │                                                   │
│         ▼                                                   │
│  ┌─────────────────────────────────────────┐               │
│  │ 1. 查找属性的安全上下文                  │               │
│  │    persist.bluetooth. → bluetooth_prop   │               │
│  └─────────────────────────────────────────┘               │
│         │                                                   │
│         ▼                                                   │
│  ┌─────────────────────────────────────────┐               │
│  │ 2. 获取当前进程的 SELinux 域             │               │
│  │    adb shell → shell 域                  │               │
│  └─────────────────────────────────────────┘               │
│         │                                                   │
│         ▼                                                   │
│  ┌─────────────────────────────────────────┐               │
│  │ 3. 检查 shell 域是否有权限               │               │
│  │    get_prop(shell, bluetooth_prop) ???   │               │
│  └─────────────────────────────────────────┘               │
│         │                                                   │
│         ▼                                                   │
│  ┌─────────────────────────────────────────┐               │
│  │ 权限检查结果                             │               │
│  │ ❌ shell 域没有 bluetooth_prop 权限      │               │
│  └─────────────────────────────────────────┘               │
│         │                                                   │
│         ▼                                                   │
│  ┌─────────────────────────────────────────┐               │
│  │ 返回空值 + 记录 SELinux 拒绝日志         │               │
│  │ avc: denied { read } for name=...        │               │
│  └─────────────────────────────────────────┘               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

六、自定义受限属性设计与实现

6.1 需求场景

假设需要添加一个自定义属性 persist.mydebug.test_property,要求:

  • 在 User 版本的 adb shell 无法获取该属性值
  • 只有特定的系统服务(如 system_server)可以读取

6.2 实现步骤

步骤一:定义 SELinux 属性类型
sepolicy 复制代码
# device/<厂商>/<设备>/sepolicy/private/property.te

# 定义新的属性类型
type mydebug_prop, property_type;

# 或者复用已有的类型(不推荐,安全性较低)
# type mydebug_prop, debug_prop;
步骤二:配置 property_contexts
plaintext 复制代码
# device/<厂商>/<设备>/sepolicy/private/property_contexts

# 将自定义属性映射到新定义的类型
persist.mydebug.    u:object_r:mydebug_prop:s0

# 或者使用精确匹配(可选)
persist.mydebug.test_property    u:object_r:mydebug_prop:s0 exact bool
步骤三:配置域权限(谁可以访问)
sepolicy 复制代码
# device/<厂商>/<设备>/sepolicy/private/system_server.te

# 允许 system_server 读取
get_prop(system_server, mydebug_prop)

# 可选:允许写入
set_prop(system_server, mydebug_prop)

# 或者只允许特定系统应用
# device/<厂商>/<设备>/sepolicy/private/my_system_app.te
get_prop(my_system_app, mydebug_prop)
sepolicy 复制代码
# device/<厂商>/<设备>/sepolicy/private/bluetooth.te

# 如果需要蓝牙服务也能读取
get_prop(bluetooth, mydebug_prop)
步骤四:配置 neverallow 规则(可选但推荐)
sepolicy 复制代码
# device/<厂商>/<设备>/sepolicy/private/mydebug.te

# 明确禁止 shell 和 untrusted_app 访问
neverallow {
    shell
    untrusted_app
    isolated_app
} mydebug_prop:file { read open };

# 或者更严格的规则
neverallow {
    domain
    -init
    -system_server
    -my_system_app
} mydebug_prop:file { read open };
步骤五:定义属性默认值(可选)
makefile 复制代码
# device/<厂商>/<设备>/system.prop

# 设置默认值
persist.mydebug.test_property=false

6.3 完整示例

目录结构
复制代码
device/<厂商>/<设备>/sepolicy/
├── private/
│   ├── property.te              # 定义属性类型
│   ├── property_contexts        # 属性到类型的映射
│   ├── system_server.te         # system_server 权限
│   ├── mydebug.te               # neverallow 规则
│   └── my_system_app.te         # 自定义系统应用权限
└── public/
    └── property.te              # 公共类型声明(如果需要)
文件内容

property.te

sepolicy 复制代码
# 定义自定义属性类型
type mydebug_prop, property_type;

# 定义更细分的类型
type mydebug_secure_prop, property_type;

property_contexts

plaintext 复制代码
# 前缀匹配
persist.mydebug.              u:object_r:mydebug_prop:s0

# 精确匹配 + 类型注解
persist.mydebug.test_property    u:object_r:mydebug_prop:s0 exact bool
persist.mydebug.secure_value     u:object_r:mydebug_secure_prop:s0 exact string

system_server.te

sepolicy 复制代码
# 允许 system_server 读取 mydebug_prop
get_prop(system_server, mydebug_prop)

# 允许 system_server 读写 mydebug_prop
set_prop(system_server, mydebug_prop)

# 允许读取安全属性
get_prop(system_server, mydebug_secure_prop)

mydebug.te

sepolicy 复制代码
# 禁止 shell 和普通应用访问
neverallow {
    shell
    untrusted_app
    isolated_app
} mydebug_prop:file { read open getattr };

# 安全属性更严格:只允许 system_server
neverallow {
    domain
    -init
    -system_server
} mydebug_secure_prop:file { read open };

6.4 验证实现

bash 复制代码
# 编译并刷入系统后验证

# 1. shell 域尝试读取(应该失败)
$ adb shell getprop persist.mydebug.test_property
# 返回空值

# 2. 查看 SELinux 拒绝日志
$ adb shell "dmesg | grep -i 'avc.*mydebug'"
# 应该看到类似:
# avc: denied { read } for name="persist.mydebug.test_property"
# scontext=u:r:shell:s0 tcontext=u:object_r:mydebug_prop:s0

# 3. 从 system_server 读取(应该成功)
# 可以通过 dumpsys 或自定义 API 验证

# 4. 临时关闭 SELinux 测试
$ adb root
$ adb shell setenforce 0
$ adb shell getprop persist.mydebug.test_property
# 此时应该能读取到值

6.5 使用自定义属性

Java 代码(需要系统权限)
java 复制代码
// 在 system_server 或系统应用中使用
public class MyDebugHelper {
    
    private static final String PROP_NAME = "persist.mydebug.test_property";
    
    /**
     * 读取属性值
     * 需要在 system_server 或有 system 权限的应用中运行
     */
    public static boolean isDebugEnabled() {
        return SystemProperties.getBoolean(PROP_NAME, false);
    }
    
    /**
     * 设置属性值
     */
    public static void setDebugEnabled(boolean enabled) {
        SystemProperties.set(PROP_NAME, enabled ? "true" : "false");
    }
}
Native 代码
cpp 复制代码
#include <cutils/properties.h>

// 读取属性
bool isDebugEnabled() {
    char value[PROP_VALUE_MAX] = {0};
    property_get("persist.mydebug.test_property", value, "false");
    return strcmp(value, "true") == 0;
}

// 设置属性
void setDebugEnabled(bool enabled) {
    property_set("persist.mydebug.test_property", enabled ? "true" : "false");
}

6.6 设计原则

复制代码
┌─────────────────────────────────────────────────────────────┐
│              自定义属性安全设计原则                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 最小权限原则                                            │
│     - 只给真正需要的域授权                                  │
│     - 优先使用 neverallow 明确禁止不需要的域                │
│                                                             │
│  2. 类型隔离原则                                            │
│     - 敏感属性使用独立的类型(如 mydebug_secure_prop)      │
│     - 不要复用已有的宽松类型                                │
│                                                             │
│  3. 命名规范                                                │
│     - 使用清晰的前缀标识属性来源                            │
│     - 如:persist.<厂商>.<模块>.<属性名>                    │
│                                                             │
│  4. 类型注解                                                │
│     - 使用 exact bool/int/string 提高类型安全               │
│     - 便于生成类型安全的 API                                │
│                                                             │
│  5. 审计日志                                                │
│     - 在 SELinux 强制模式下测试验证                         │
│     - 检查是否有意外的拒绝日志                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

六、源码深入分析

5.1 init 进程中的属性服务

5.1.1 属性服务启动流程
cpp 复制代码
// system/core/init/init.cpp

int main(int argc, char** argv) {
    // ...
    
    // 初始化属性服务
    property_init();
    
    // 加载属性定义文件
    load_properties_from_file(PROP_PATH_SYSTEM_BUILD);
    load_properties_from_file(PROP_PATH_VENDOR_BUILD);
    load_properties_from_file(PROP_PATH_ODM_BUILD);
    
    // 启动属性服务
    start_property_service();
    
    // ...
}
5.1.2 属性服务实现
cpp 复制代码
// system/core/init/property_service.cpp

void start_property_service() {
    // 创建属性服务 socket
    property_set_fd = CreateSocket(PROP_SERVICE_NAME, 
                                    SOCK_STREAM | SOCK_CLOEXEC, 
                                    0666, 
                                    0, 0, 
                                    &error);
    
    if (property_set_fd == -1) {
        LOG(ERROR) << "Failed to create socket: " << error;
        return;
    }
    
    // 监听连接
    listen(property_set_fd, 8);
    
    // 注册到 epoll
    register_epoll_handler(property_set_fd, handle_property_set_fd);
}

// 处理属性获取请求
static void handle_property_get(SocketConnection& socket) {
    std::string name;
    std::string value;
    
    // 接收属性名
    if (!socket.RecvString(&name)) {
        return;
    }
    
    // 检查属性访问权限(关键点)
    if (!is_property_gettable(name)) {
        // 属性不可获取,返回空值
        value = "";
    } else {
        // 正常获取属性值
        value = android::base::GetProperty(name, "");
    }
    
    // 发送属性值
    socket.SendString(value);
}

5.2 属性可获取性检查

cpp 复制代码
// system/core/init/property_service.cpp

// 检查属性是否可获取
static bool is_property_gettable(const std::string& name) {
    // 1. 检查 SELinux 权限
    if (!CheckSELinuxAccess(name, "get")) {
        return false;
    }
    
    // 2. 检查构建类型限制
    if (IsProductionBuild() && IsRestrictedProperty(name)) {
        return false;
    }
    
    return true;
}

// 检查是否为受限属性
static bool IsRestrictedProperty(const std::string& name) {
    // 检查前缀匹配
    static const char* restricted_prefixes[] = {
        "debug.",
        "ro.debuggable",
        // ... 其他受限前缀
    };
    
    for (const char* prefix : restricted_prefixes) {
        if (android::base::StartsWith(name, prefix)) {
            return true;
        }
    }
    
    return false;
}

5.3 Bionic 层属性获取

cpp 复制代码
// bionic/libc/bionic/system_property_api.cpp

// 系统属性获取 API
int __system_property_get(const char* name, char* value) {
    // 查找属性
    const prop_info* pi = __system_property_find(name);
    
    if (pi != nullptr) {
        // 读取属性值
        __system_property_read_callback(pi, 
            [](void* cookie, const char*, const char* value, uint32_t) {
                strcpy(static_cast<char*>(cookie), value);
            }, 
            value);
        return strlen(value);
    }
    
    // 属性不存在,返回空
    value[0] = '\0';
    return 0;
}

5.4 Java 层属性获取

java 复制代码
// frameworks/base/core/java/android/os/SystemProperties.java

public class SystemProperties {
    
    /**
     * 获取系统属性值
     * @param key 属性名称
     * @return 属性值,不存在时返回空字符串
     */
    @NonNull
    public static String get(@NonNull String key) {
        // 调用 native 方法
        return native_get(key);
    }
    
    /**
     * 获取系统属性值,带默认值
     */
    @NonNull
    public static String get(@NonNull String key, @Nullable String def) {
        if (def == null) def = "";
        return native_get(key, def);
    }
    
    // Native 方法声明
    @FastNative
    private static native String native_get(String key);
    
    @FastNative
    private static native String native_get(String key, String def);
}

对应的 Native 实现:

cpp 复制代码
// frameworks/base/core/jni/android_os_SystemProperties.cpp

static jstring SystemProperties_getSS(JNIEnv* env, jclass clazz, 
                                       jstring keyJ, jstring defJ) {
    // 转换 Java 字符串
    ScopedUtfChars key(env, keyJ);
    if (key.c_str() == NULL) {
        return NULL;
    }
    
    // 获取属性值
    char buf[PROPERTY_VALUE_MAX];
    const char* def = defJ ? env->GetStringUTFChars(defJ, NULL) : "";
    
    // 调用 __system_property_get
    int len = __system_property_get(key.c_str(), buf);
    
    if (len <= 0) {
        // 属性不存在或不可访问,返回默认值
        if (defJ) {
            jstring result = env->NewStringUTF(def);
            env->ReleaseStringUTFChars(defJ, def);
            return result;
        }
        return env->NewStringUTF("");
    }
    
    return env->NewStringUTF(buf);
}

七、SELinux 权限控制详解(核心机制)详解

7.1 属性读取权限的真实机制

在 Android 14/15 中,SELinux 对属性读取实施了严格的权限控制。这与早期的理解不同:属性读取并非直接从共享内存无权限获取,而是需要经过 SELinux 权限检查

7.1.1 属性读取的 SELinux 检查点
cpp 复制代码
// bionic/libc/bionic/system_property_api.cpp

int __system_property_get(const char* name, char* value) {
    // 1. 查找属性
    const prop_info* pi = __system_property_find(name);
    
    if (pi != nullptr) {
        // 2. 读取属性前,内核会检查 SELinux 权限
        // 这一步是通过文件系统权限实现的
        __system_property_read_callback(pi, ...);
        return strlen(value);
    }
    
    value[0] = '\0';
    return 0;
}
7.1.2 属性文件的 SELinux 标签
bash 复制代码
# 查看属性文件的 SELinux 上下文
$ adb shell ls -Z /dev/__properties__/

# 示例输出
# u:object_r:properties_device:s0 ...
# 每个属性文件都有对应的安全上下文

7.2 property_contexts 文件详解

7.2.1 文件位置

property_contexts 文件分布在多个位置,最终会被合并:

bash 复制代码
# 系统属性上下文
/system/etc/selinux/plat_property_contexts
/system/etc/selinux/system_property_contexts

# 厂商属性上下文
/vendor/etc/selinux/vendor_property_contexts
/vendor/etc/selinux/nonplat_property_contexts

# 产品属性上下文
/product/etc/selinux/product_property_contexts

# ODM 属性上下文
/odm/etc/selinux/odm_property_contexts
7.2.2 property_contexts 语法格式
plaintext 复制代码
# 基本语法格式
# 属性名模式    SELinux 安全上下文    [类型注解]

# 格式说明:
# 1. 属性名模式:支持通配符和精确匹配
# 2. SELinux 上下文:u:object_r:类型:s0
# 3. 类型注解(可选):exact string/bool/int 等
7.2.3 实际配置示例解析

根据您提供的配置:

plaintext 复制代码
# 示例 1:前缀匹配
persist.audio.          u:object_r:audio_prop:s0
persist.bluetooth.      u:object_r:bluetooth_prop:s0
persist.nfc.            u:object_r:nfc_prop:s0

# 示例 2:精确匹配 + 类型注解
persist.bluetooth.a2dp_offload.cap                          u:object_r:bluetooth_a2dp_offload_prop:s0 exact string
persist.bluetooth.a2dp_offload.disabled                     u:object_r:bluetooth_a2dp_offload_prop:s0 exact bool

详细解析:

配置项 匹配规则 SELinux 类型 类型注解 说明
persist.audio. 前缀匹配 audio_prop 匹配所有 persist.audio.* 属性
persist.bluetooth. 前缀匹配 bluetooth_prop 匹配所有 persist.bluetooth.* 属性(除非被更具体的规则覆盖)
persist.bluetooth.a2dp_offload.cap 精确匹配 bluetooth_a2dp_offload_prop exact string 仅匹配此属性,类型为字符串
persist.bluetooth.a2dp_offload.disabled 精确匹配 bluetooth_a2dp_offload_prop exact bool 仅匹配此属性,类型为布尔值
7.2.4 类型注解详解(Android 10+)

Android 10 引入了类型注解,用于系统属性的类型安全:

plaintext 复制代码
# 类型注解语法
属性名    SELinux上下文    exact <类型>

# 支持的类型注解:
# - exact bool     布尔类型(true/false)
# - exact int      整数类型
# - exact string   字符串类型
# - exact enum     枚举类型(需要指定值列表)

类型注解的作用:

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    类型注解的作用                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 编译时类型检查                                          │
│     - 确保属性值与声明的类型匹配                             │
│     - 防止类型错误                                           │
│                                                             │
│  2. 生成类型安全的 API(Android 10+)                       │
│     - 系统会自动生成 Java/C++ API                            │
│     - 开发者可以通过类型安全的方法访问属性                    │
│                                                             │
│  3. 属性文档化                                               │
│     - 明确属性的预期类型                                     │
│     - 便于代码理解和维护                                     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

代码示例:

java 复制代码
// Android 10+ 提供的类型安全 API
// 位于 frameworks/base/core/java/android/sysprop/BluetoothProperties.java

public class BluetoothProperties {
    // 自动生成的类型安全方法
    public static boolean isA2dpOffloadDisabled() {
        // 对应:persist.bluetooth.a2dp_offload.disabled exact bool
        return SystemProperties.getBoolean(
            "persist.bluetooth.a2dp_offload.disabled", false);
    }
    
    public static void setA2dpOffloadDisabled(boolean value) {
        SystemProperties.set(
            "persist.bluetooth.a2dp_offload.disabled", 
            value ? "true" : "false");
    }
    
    public static String getA2dpOffloadCap() {
        // 对应:persist.bluetooth.a2dp_offload.cap exact string
        return SystemProperties.get("persist.bluetooth.a2dp_offload.cap", "");
    }
}
7.2.5 属性匹配优先级规则
复制代码
┌─────────────────────────────────────────────────────────────┐
│                 属性匹配优先级(从高到低)                    │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 精确匹配(无通配符)                                     │
│     persist.bluetooth.a2dp_offload.disabled                 │
│     ↓                                                       │
│     使用 bluetooth_a2dp_offload_prop                        │
│                                                             │
│  2. 更长前缀匹配                                             │
│     persist.bluetooth.a2dp_offload.                         │
│     ↓                                                       │
│     覆盖更短的 persist.bluetooth.                           │
│                                                             │
│  3. 较短前缀匹配                                             │
│     persist.bluetooth.                                      │
│     ↓                                                       │
│     作为默认规则匹配所有未被精确匹配的属性                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

示例:
persist.bluetooth.disableabsvol
    → 匹配 persist.bluetooth. → bluetooth_prop

persist.bluetooth.a2dp_offload.disabled
    → 精确匹配 → bluetooth_a2dp_offload_prop

persist.bluetooth.a2dp_offload.custom
    → 匹配 persist.bluetooth. → bluetooth_prop
    (因为没有 a2dp_offload.custom 的精确匹配)
7.2.6 属性作用域与访问权限

关键发现:属性的作用域由 SELinux 类型决定,而非属性名称前缀。

plaintext 复制代码
# 不同 SELinux 类型对应不同的访问权限

# bluetooth_prop 类型
persist.bluetooth.      u:object_r:bluetooth_prop:s0
# 谁可以访问?取决于哪些域有 get_prop(*, bluetooth_prop) 权限

# bluetooth_a2dp_offload_prop 类型
persist.bluetooth.a2dp_offload.cap      u:object_r:bluetooth_a2dp_offload_prop:s0
persist.bluetooth.a2dp_offload.disabled u:object_r:bluetooth_a2dp_offload_prop:s0
# 可能有不同的访问权限配置

SELinux 类型到访问权限的映射:

sepolicy 复制代码
# system/sepolicy/private/bluetooth.te

# bluetooth 域可以访问 bluetooth_prop
get_prop(bluetooth, bluetooth_prop)
set_prop(bluetooth, bluetooth_prop)

# bluetooth 域可以访问 bluetooth_a2dp_offload_prop
get_prop(bluetooth, bluetooth_a2dp_offload_prop)
set_prop(bluetooth, bluetooth_a2dp_offload_prop)

# system/sepolicy/private/system_server.te

# system_server 可以读取蓝牙属性
get_prop(system_server, bluetooth_prop)
get_prop(system_server, bluetooth_a2dp_offload_prop)

# shell 域 - 通常无法访问
# 没有 get_prop(shell, bluetooth_prop) 权限
7.2.7 查看设备上的实际配置
bash 复制代码
# 查看系统属性上下文
$ adb shell cat /system/etc/selinux/plat_property_contexts | grep bluetooth

# 查看厂商属性上下文
$ adb shell cat /vendor/etc/selinux/vendor_property_contexts | grep bluetooth

# 查看所有属性上下文
$ adb shell cat /system/etc/selinux/plat_property_contexts
$ adb shell cat /vendor/etc/selinux/vendor_property_contexts

# 搜索特定属性的安全上下文
$ adb shell "grep 'persist.bluetooth' /system/etc/selinux/plat_property_contexts"
$ adb shell "grep 'persist.bluetooth' /vendor/etc/selinux/vendor_property_contexts"

7.3 如何验证 shell 域没有 bluetooth_prop 权限

7.3.1 SELinux 权限机制说明

在 SELinux 中,权限遵循"默认拒绝"原则:

复制代码
┌─────────────────────────────────────────────────────────────┐
│              SELinux 权限判断机制                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  默认规则:除非明确允许,否则拒绝                            │
│                                                             │
│  要让 shell 域能读取 bluetooth_prop,必须有明确的规则:      │
│  get_prop(shell, bluetooth_prop)                            │
│                                                             │
│  如果没有这条规则 → SELinux 拒绝访问                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘
7.3.2 查看 shell.te 源码

源码位置system/sepolicy/public/shell.tesystem/sepolicy/private/shell.te

sepolicy 复制代码
# system/sepolicy/public/shell.te

# shell 域定义
type shell, domain, mlstrustedsubject;
type shell_exec, system_file_type, exec_type, file_type;

# 网络权限
net_domain(shell)

# 日志权限
read_logd(shell)
control_logd(shell)

# 文件访问权限
allow shell shell_data_file:dir create_dir_perms;
allow shell shell_data_file:file create_file_perms;
# ... 其他文件权限

# 属性读取权限 - 注意这里列出的是允许的,没有列出的就是不允许!
# shell 域可以读取以下类型的属性:
get_prop(shell, shell_prop)           # shell 相关属性
get_prop(shell, default_prop)         # 默认属性
get_prop(shell, log_prop)             # 日志属性
get_prop(shell, system_prop)          # 系统属性
get_prop(shell, boot_status_prop)     # 启动状态
get_prop(shell, toolbox_prop)         # toolbox 属性

# ⚠️ 注意:没有 get_prop(shell, bluetooth_prop)
# 因此 shell 无法读取 bluetooth_prop 类型的属性

关键理解 :SELinux 使用白名单机制,只有明确列出的权限才被允许。bluetooth_prop 没有在 shell 的权限列表中,所以被拒绝。

7.3.3 在设备上验证方法

方法一:查看 SELinux 拒绝日志

bash 复制代码
# 读取属性时查看 dmesg
$ adb shell "dmesg | grep -i 'avc.*bluetooth.*prop'"

# 或查看 logcat
$ adb shell "logcat -b all | grep -i 'avc denied.*bluetooth_prop'"

# 典型的拒绝日志示例:
# avc: denied { read } for name="persist.bluetooth.disableabsvol"
# dev="properties" ino=12345 scontext=u:r:shell:s0
# tcontext=u:object_r:bluetooth_prop:s0 tclass=file permissive=0

日志解读

  • scontext=u:r:shell:s0:源上下文是 shell 域
  • tcontext=u:object_r:bluetooth_prop:s0:目标上下文是 bluetooth_prop 类型
  • denied { read }:读取权限被拒绝
  • permissive=0:SELinux 处于强制模式,拒绝生效

方法二:临时关闭 SELinux 验证

bash 复制代码
# 查看当前 SELinux 模式
$ adb shell getenforce
Enforcing  # 强制模式

# 临时设置为宽容模式(需要 root)
$ adb root
$ adb shell setenforce 0

# 再次尝试读取属性
$ adb shell getprop persist.bluetooth.disableabsvol
# 如果此时能读取,说明确实是 SELinux 限制

# 恢复强制模式
$ adb shell setenforce 1

方法三:查看编译后的策略文件

bash 复制代码
# 查看系统属性上下文
$ adb shell cat /system/etc/selinux/plat_property_contexts | grep bluetooth

# 输出示例:
# persist.bluetooth.    u:object_r:bluetooth_prop:s0

# 查看 shell 域的策略(需要 root)
$ adb root
$ adb shell sesearch --allow -s shell -c file -p read

# 或查看策略源码
$ adb shell cat /system/etc/selinux/plat_sepolicy.cil | grep -A5 "bluetooth_prop"

方法四:使用 sesearch 工具(如果可用)

bash 复制代码
# 搜索 shell 域是否有 bluetooth_prop 的读取权限
$ sesearch -A -s shell -t bluetooth_prop -c file -p read

# 如果没有输出,表示没有权限
# 如果有输出,会显示类似:
# allow shell bluetooth_prop:file { read open };
7.3.4 查看哪些域有 bluetooth_prop 权限
bash 复制代码
# 搜索所有可以读取 bluetooth_prop 的域
$ adb shell sesearch -A -t bluetooth_prop -c file -p read

# 典型输出:
# allow bluetooth bluetooth_prop:file { read open };
# allow system_server bluetooth_prop:file { read open };
# allow init bluetooth_prop:file { read open };
# 注意:shell 不在列表中!
7.3.5 bluetooth.te 中的权限定义
sepolicy 复制代码
# system/sepolicy/private/bluetooth.te

# bluetooth 域可以访问 bluetooth_prop
get_prop(bluetooth, bluetooth_prop)
set_prop(bluetooth, bluetooth_prop)

# bluetooth 域也可以访问 bluetooth_a2dp_offload_prop
get_prop(bluetooth, bluetooth_a2dp_offload_prop)
set_prop(bluetooth, bluetooth_a2dp_offload_prop)
7.3.6 system_server.te 中的权限定义
sepolicy 复制代码
# system/sepolicy/private/system_server.te

# system_server 域可以读取蓝牙属性
get_prop(system_server, bluetooth_prop)
get_prop(system_server, bluetooth_a2dp_offload_prop)

# 但在 User 版本中可能限制写入
# userdebug_or_eng(`
#     set_prop(system_server, bluetooth_prop)
# ')

7.4 SELinux neverallow 规则(禁止性规则)

除了"没有明确允许就是拒绝"的默认规则外,SELinux 还有 neverallow 规则来明确禁止某些访问:

sepolicy 复制代码
# system/sepolicy/public/domain.te 或相关文件

# 明确禁止非蓝牙域写入蓝牙属性
neverallow {
    domain
    -bluetooth
    -init
    -system_server
} bluetooth_prop:property_service set;

# 明确禁止 shell/untrusted_app 读取某些敏感属性
# 虽然没有 allow 规则,但 neverallow 提供了额外的安全保障

neverallow 的作用

  1. 防止策略编写者意外添加不安全的权限
  2. 编译时检查,如果违反 neverallow 规则,编译会失败
  3. 提供文档化的安全边界

7.5 属性权限完整对照表

属性类型 (prop) shell untrusted_app system_server bluetooth init
default_prop ✅ 读 ✅ 读 ✅ 读/写 ✅ 读 ✅ 读/写
system_prop ✅ 读 ✅ 读 ✅ 读/写 ✅ 读 ✅ 读/写
shell_prop ✅ 读/写 ✅ 读 ✅ 读 ✅ 读/写
bluetooth_prop ✅ 读 ✅ 读/写 ✅ 读/写
bluetooth_a2dp_offload_prop ✅ 读 ✅ 读/写 ✅ 读/写
debug_prop ❌ (userdebug/eng 可读) ✅ 读 (userdebug/eng) ✅ 读 (userdebug/eng) ✅ 读/写
log_prop ✅ 读 ✅ 读 ✅ 读/写 ✅ 读 ✅ 读/写
nfc_prop ✅ 读 ✅ 读/写
audio_prop ✅ 读/写 ✅ 读/写

说明

  • ✅ 读 = 有 get_prop 权限
  • ✅ 读/写 = 有 get_propset_prop 权限
  • ❌ = 无权限(SELinux 默认拒绝)

7.6 SELinux 编译宏详解

makefile 复制代码
# system/sepolicy/public/te_macros

# 条件编译宏定义
define(`userdebug_or_eng', ifelse(target_build_variant, `user', , $1))

# 使用示例
userdebug_or_eng(`
    # 仅在 userdebug/eng 版本执行的规则
    allow shell debug_prop:file { read open };
')

7.7 属性权限层级图

复制代码
┌─────────────────────────────────────────────────────────────┐
│              属性访问权限层级(按权限从高到低)              │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ init 域                                              │   │
│  │ - 可读写所有属性                                      │   │
│  │ - 属性服务的拥有者                                    │   │
│  └─────────────────────────────────────────────────────┘   │
│                           │                                 │
│                           ▼                                 │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ system_server / system_app 域                        │   │
│  │ - 可读取大部分系统属性                                │   │
│  │ - 可写入部分系统属性                                  │   │
│  │ - 可读取 bluetooth_prop                              │   │
│  └─────────────────────────────────────────────────────┘   │
│                           │                                 │
│                           ▼                                 │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ bluetooth 域                                         │   │
│  │ - 可读写蓝牙相关属性                                  │   │
│  │ - 可读取 bluetooth_prop                              │   │
│  └─────────────────────────────────────────────────────┘   │
│                           │                                 │
│                           ▼                                 │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ shell 域(adb shell)                                │   │
│  │ - 可读取 default_prop、system_prop、log_prop         │   │
│  │ - ❌ 不可读取 bluetooth_prop(User 版本)            │   │
│  │ - ❌ 不可读取 debug_prop(User 版本)                │   │
│  └─────────────────────────────────────────────────────┘   │
│                           │                                 │
│                           ▼                                 │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ untrusted_app 域(普通应用)                          │   │
│  │ - 仅可读取少量公开属性                                │   │
│  │ - ❌ 不可读取 bluetooth_prop                         │   │
│  │ - ❌ 不可读取 debug_prop                             │   │
│  │ - ❌ 不可读取大部分 persist.* 属性                   │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
└─────────────────────────────────────────────────────────────┘

6.1 SELinux 属性上下文

SELinux 为每个系统属性定义了安全上下文:

plaintext 复制代码
# system/sepolicy/private/property_contexts

# 调试属性
debug.                  u:object_r:debug_prop:s0

# 蓝牙相关
bluetooth.              u:object_r:bluetooth_prop:s0
ro.bluetooth.           u:object_r:bluetooth_prop:s0

# 系统属性
ro.                     u:object_r:default_prop:s0
sys.                    u:object_r:system_prop:s0
persist.                u:object_r:persist_prop:s0

6.2 SELinux 权限规则

sepolicy 复制代码
# system/sepolicy/private/property.te

# 调试属性类型定义
type debug_prop, property_type;

# 蓝牙属性类型定义
type bluetooth_prop, property_type;
type bluetooth_debug_prop, property_type;

# 权限规则
# 仅特定域可以读取 debug_prop
userdebug_or_eng(`
  allow domain debug_prop:file { read open };
')

# User 版本限制
neverallow { domain -init -system_server -dumpstate } debug_prop:file { read open };

6.3 域权限配置

sepolicy 复制代码
# system/sepolicy/private/system_server.te

# 允许 system_server 读取蓝牙调试属性(仅 userdebug/eng)
userdebug_or_eng(`
  allow system_server bluetooth_debug_prop:file { read open };
')

# User 版本禁止
neverallow system_server bluetooth_debug_prop:file { read open };

6.4 SELinux 策略编译宏

userdebug_or_eng 宏的实际定义 (位于 system/sepolicy/public/te_macros):

m4 复制代码
#####################################
# SELinux rules which apply only to userdebug or eng builds
#
define(`userdebug_or_eng', ifelse(target_build_variant, `eng', $1, ifelse(target_build_variant, `userdebug', $1)))

其他相关条件宏(并非都在 AOSP 中定义,部分由厂商自定义):

m4 复制代码
# 可能的条件宏定义
define(`user_build', ifelse(target_build_variant, `user', $1))
define(`eng_build', ifelse(target_build_variant, `eng', $1))
define(`userdebug_build', ifelse(target_build_variant, `userdebug', $1))

m4 ifelse 语法说明

  • ifelse(A, B, C, D) - 如果 A 等于 B,返回 C,否则返回 D
  • ifelse(A, B, C, ifelse(D, E, F, G)) - 嵌套条件判断

八、构建类型差异对比

8.1 构建类型说明

Android 构建类型分为三种:

构建类型 说明 典型用途
eng 工程版本 内部开发、调试
userdebug 调试版本 测试、验证
user 用户版本 正式发布、量产

8.2 关键差异:userdebug vs user 的 SELinux 策略

您观察到的现象

  • userdebug 版本:adb shell getprop persist.bluetooth.* 可以读取
  • user 版本:adb shell getprop persist.bluetooth.* 返回空值

原因分析 :这正是 userdebug_or_eng 宏的作用!

8.2.1 SELinux 策略的条件编译

SELinux 策略源码中使用 userdebug_or_eng 宏来定义条件权限。

userdebug_or_eng 宏的真实定义 (位于 system/sepolicy/public/te_macros):

m4 复制代码
#####################################
# SELinux rules which apply only to userdebug or eng builds
#
define(`userdebug_or_eng', ifelse(target_build_variant, `eng', $1, ifelse(target_build_variant, `userdebug', $1)))

宏展开逻辑

  • 如果 target_build_variant = eng → 展开为 $1
  • 如果 target_build_variant = userdebug → 展开为 $1
  • 如果 target_build_variant = user → 返回空(规则被移除)

重要说明 :在标准 AOSP 中,shell 域对 bluetooth_prop 的读取权限并非 通过 userdebug_or_eng 条件授予的。实际上:

  1. 标准 AOSP 中 shell 域没有 bluetooth_prop 权限(所有版本)
  2. 如果您在 userdebug 版本能读取 persist.bluetooth.* ,可能是:
    • 厂商定制的 SELinux 策略添加了额外权限
    • 设备使用了 root 权限或临时关闭了 SELinux
    • 厂商在 device/<厂商>/<设备>/sepolicy/ 中添加了 get_prop(shell, bluetooth_prop)

AOSP 中典型的 userdebug_or_eng 使用场景

sepolicy 复制代码
# system/sepolicy/private/shell.te

# shell 域在 userdebug/eng 版本获得的额外权限
userdebug_or_eng(`
    # 调试文件系统访问
    allow shell debugfs_tracing_debug:file rw_file_perms;
    allow shell debugfs_tracing:file rw_file_perms;
')

# system/sepolicy/private/domain.te
# 所有域在 userdebug/eng 版本获得的权限
userdebug_or_eng(`
    allow domain debugfs_tracing_debug:file rw_file_perms;
')
8.2.2 userdebug_or_eng 宏定义
makefile 复制代码
# system/sepolicy/public/te_macros

# 宏定义:仅在 userdebug 和 eng 版本生效
define(`userdebug_or_eng', 
    ifelse(target_build_variant, `user', , $1)
)

# 展开说明:
# 如果 target_build_variant == "user",则不执行 $1
# 如果 target_build_variant == "userdebug" 或 "eng",则执行 $1
8.2.3 构建类型与策略编译的关系
复制代码
┌─────────────────────────────────────────────────────────────┐
│           SELinux 策略编译流程                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  源码(shell.te)                                           │
│  ┌─────────────────────────────────────────────┐           │
│  │ # 基础权限(所有版本都有)                   │           │
│  │ get_prop(shell, system_prop)                 │           │
│  │ get_prop(shell, default_prop)                │           │
│  │                                              │           │
│  │ # 条件权限                                   │           │
│  │ userdebug_or_eng(`                           │           │
│  │     get_prop(shell, bluetooth_prop)          │           │
│  │     get_prop(shell, debug_prop)              │           │
│  │ ')                                           │           │
│  └─────────────────────────────────────────────┘           │
│                         │                                   │
│                         ▼                                   │
│  ┌─────────────────────────────────────────────┐           │
│  │           构建类型判断                        │           │
│  │ target_build_variant = ?                     │           │
│  └─────────────────────────────────────────────┘           │
│                         │                                   │
│         ┌───────────────┼───────────────┐                  │
│         │               │               │                  │
│         ▼               ▼               ▼                  │
│    ┌─────────┐    ┌──────────┐    ┌─────────┐             │
│    │  eng    │    │userdebug │    │  user   │             │
│    └────┬────┘    └────┬─────┘    └────┬────┘             │
│         │              │               │                   │
│         │              │               ▼                   │
│         │              │    ┌─────────────────────┐       │
│         │              │    │ 编译结果:          │       │
│         │              │    │ get_prop(shell,     │       │
│         │              │    │   system_prop)      │       │
│         │              │    │ get_prop(shell,     │       │
│         │              │    │   default_prop)     │       │
│         │              │    │ ❌ 没有 bluetooth_prop│      │
│         │              │    └─────────────────────┘       │
│         │              │                                   │
│         └──────────────┴──────────────┐                   │
│                                       ▼                    │
│                       ┌─────────────────────────────┐     │
│                       │ 编译结果(eng/userdebug):  │     │
│                       │ get_prop(shell, system_prop)│     │
│                       │ get_prop(shell, default_prop)│    │
│                       │ ✅ get_prop(shell,          │     │
│                       │      bluetooth_prop)        │     │
│                       │ ✅ get_prop(shell, debug_prop)│    │
│                       └─────────────────────────────┘     │
│                                                             │
└─────────────────────────────────────────────────────────────┘

8.3 实际验证方法

8.3.1 查看 SELinux 策略差异
bash 复制代码
# 在 userdebug 版本上查看 shell 域的权限
$ adb shell sesearch -A -s shell -c file -p read

# 输出可能包含:
# allow shell bluetooth_prop:file { read open };
# allow shell debug_prop:file { read open };

# 在 user 版本上执行相同命令
# 上述权限将不存在
8.3.2 检查构建类型
bash 复制代码
# 查看当前构建类型
$ adb shell getprop ro.build.type
userdebug  # 或 user

# 查看是否可调试
$ adb shell getprop ro.debuggable
1  # userdebug 版本
0  # user 版本
8.3.3 查看 SELinux 策略源码
bash 复制代码
# 在设备上查看编译后的策略
$ adb shell cat /sys/fs/selinux/policy | strings | grep -i shell

# 或查看策略数据库
$ adb shell cat /sys/fs/selinux/policy

8.4 属性访问权限对比

属性前缀 eng 版本 userdebug 版本 user 版本
ro.* ✅ 可读 ✅ 可读 ✅ 可读
sys.* ✅ 可读写 ✅ 可读写 ✅ 可读写
persist.sys.* ✅ 可读写 ✅ 可读写 ✅ 可读写
persist.bluetooth.* ✅ 可读 ✅ 可读 ❌ shell 无法读取
persist.nfc.* ✅ 可读 ✅ 可读 ❌ shell 无法读取
persist.audio.* ✅ 可读 ✅ 可读 ❌ shell 无法读取
debug.* ✅ 可读写 ✅ 可读写 ❌ shell 无法读取
log.tag.* ✅ 可读写 ✅ 可读写 ⚠️ 部分受限

说明 :userdebug 和 eng 版本的 shell 域拥有额外的属性读取权限,这些权限通过 userdebug_or_eng 宏条件编译。

8.5 构建配置文件

makefile 复制代码
# build/core/main.mk

# 构建类型定义
ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
  # Debug 构建配置
  ADDITIONAL_DEFAULT_PROPERTIES += \
      ro.debuggable=1 \
      ro.adb.secure=0 \
      persist.sys.usb.config=adb
      
  # SELinux 策略会包含 userdebug_or_eng 中的规则
else
  # User 构建配置
  ADDITIONAL_DEFAULT_PROPERTIES += \
      ro.debuggable=0 \
      ro.adb.secure=1 \
      persist.sys.usb.config=none
      
  # SELinux 策略不包含 userdebug_or_eng 中的规则
endif

8.6 为什么要有这个差异?

复制代码
┌─────────────────────────────────────────────────────────────┐
│              构建类型差异的安全考量                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  userdebug/eng 版本:                                       │
│  ├── 面向开发者和测试人员                                   │
│  ├── 需要调试能力来定位问题                                 │
│  ├── 需要访问敏感属性来调试系统行为                         │
│  └── 设备通常在受控环境中使用                               │
│                                                             │
│  user 版本:                                                │
│  ├── 面向最终用户                                           │
│  ├── 需要最大化的安全性                                     │
│  ├── 防止恶意应用或攻击者获取敏感信息                       │
│  ├── 符合安全认证要求                                       │
│  └── 设备在不可控的公开环境中使用                           │
│                                                             │
│  安全原则:最小权限原则                                     │
│  - User 版本只保留必要的基础权限                            │
│  - 移除所有调试和开发相关的额外权限                         │
│                                                             │
└─────────────────────────────────────────────────────────────┘

8.7 userdebug_or_eng 宏的定义位置

8.7.1 宏定义来源

userdebug_or_eng 宏是通过 m4 宏处理器 在 SELinux 策略编译时进行条件展开的。其定义位置在不同的 Android 版本中有所变化:

Android 版本 定义位置 说明
Android 8.x - 10 system/sepolicy/public/te_macros 在 te_macros 文件中直接定义
Android 11+ 构建系统动态注入 通过 BOARD_SEPOLICY_M4DEFS 传递

重要说明 :在较新的 Android 版本中,您查看 system/sepolicy/Android.mk 时可能只看到 BOARD_SEPOLICY_M4DEFS 的处理代码:

makefile 复制代码
# system/sepolicy/Android.mk
# 您看到的代码

ifdef BOARD_SEPOLICY_M4DEFS
LOCAL_ADDITIONAL_M4DEFS := $(addprefix -D, $(BOARD_SEPOLICY_M4DEFS))
else
LOCAL_ADDITIONAL_M4DEFS :=
endif

这是正常的!userdebug_or_eng 宏的实际定义是在 构建系统的其他位置 注入到 BOARD_SEPOLICY_M4DEFS 变量中的。

8.7.2 宏定义的真实注入流程
复制代码
┌─────────────────────────────────────────────────────────────┐
│           userdebug_or_eng 宏的完整注入流程                  │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  1. 构建系统初始化(build/core/main.mk 或类似文件)          │
│     ├── 读取 TARGET_BUILD_VARIANT                           │
│     │   ├── user                                            │
│     │   ├── userdebug                                       │
│     │   └── eng                                             │
│     │                                                       │
│     └── 根据 BUILD_VARIANT 设置 BOARD_SEPOLICY_M4DEFS       │
│                                                             │
│  2. 可能的定义位置(不同版本不同):                         │
│                                                             │
│     方式A - 在 build/core/config.mk 或 main.mk 中:          │
│     ┌─────────────────────────────────────────────┐         │
│     │ ifeq ($(TARGET_BUILD_VARIANT),user)          │         │
│     │     # User 版本:宏展开为空                  │         │
│     │     BOARD_SEPOLICY_M4DEFS += \               │         │
│     │         userdebug_or_eng=\#                  │         │
│     │ else                                         │         │
│     │     # userdebug/eng 版本:宏展开为参数       │         │
│     │     BOARD_SEPOLICY_M4DEFS += \               │         │
│     │         userdebug_or_eng=\$1                 │         │
│     │ endif                                        │         │
│     └─────────────────────────────────────────────┘         │
│                                                             │
│     方式B - 在 system/sepolicy/definitions 稡块中定义        │
│     (Android 12+ 使用 Soong/Blueprint)                     │
│                                                             │
│     方式C - 预定义在 te_macros 文件中(旧版本):            │
│     ┌─────────────────────────────────────────────┐         │
│     │ # system/sepolicy/public/te_macros           │         │
│     │ define(`userdebug_or_eng',                   │         │
│     │     ifelse(target_build_variant, `user', , $1) │       │
│     │ )                                            │         │
│     └─────────────────────────────────────────────┘         │
│                                                             │
│  3. Android.mk 处理 BOARD_SEPOLICY_M4DEFS                    │
│     ┌─────────────────────────────────────────────┐         │
│     │ ifdef BOARD_SEPOLICY_M4DEFS                  │         │
│     │     LOCAL_ADDITIONAL_M4DEFS := \             │         │
│     │         $(addprefix -D, $(BOARD_SEPOLICY_M4DEFS)) │   │
│     │ endif                                        │         │
│     │ # 生成类似:-Duserdebug_or_eng=$1            │         │
│     └─────────────────────────────────────────────┘         │
│                                                             │
│  4. m4 处理器编译策略文件                                    │
│     m4 ${LOCAL_ADDITIONAL_M4DEFS} *.te → policy.conf        │
│                                                             │
│  5. 策略编译结果                                             │
│     ├── User 版本:userdebug_or_eng(...) → (空)             │
│     └── userdebug/eng:userdebug_or_eng($1) → $1            │
│                                                             │
└─────────────────────────────────────────────────────────────┘
8.7.3 如何查找宏的实际定义位置
bash 复制代码
# 方法一:搜索整个源码树
# 在 AOSP 源码根目录执行
grep -r "userdebug_or_eng" build/core/
grep -r "userdebug_or_eng" system/sepolicy/

# 方法二:查看 te_macros 文件
cat system/sepolicy/public/te_macros | grep -A5 "userdebug_or_eng"

# 方法三:查看编译过程中的 m4 参数
# 编译时查看输出
make sepolicy -j8 2>&1 | grep -i "m4def"

# 方法四:查看最终生成的策略
# 编译后在 out 目录查看
cat out/target/product/<device>/obj/ETC/sepolicy_intermediates/policy.conf
# 检查 userdebug_or_eng 规则是否被展开
8.7.4 Android 12+ 的变化(Soong 构建系统)

从 Android 12 开始,SELinux 策略编译逐渐迁移到 Soong 构建系统:

bp 复制代码
// system/sepolicy/Android.bp (简化示例)

se_policy_conf {
    name: "sepolicy",
    srcs: ["**/*.te"],
    m4defs: [
        // 条件宏可能在这里定义
        // 或者从全局配置中继承
    ],
}

关键点:无论宏在哪里定义,其核心机制相同:

  1. 构建类型判断 :检查 TARGET_BUILD_VARIANT
  2. 宏展开控制
    • user 版本:userdebug_or_eng(...) → 空(规则被移除)
    • userdebug/eng 版本:userdebug_or_eng(...) → 展开为参数内容
8.7.5 te_macros 文件中的典型定义(旧版本参考)
m4 复制代码
# system/sepolicy/public/te_macros (旧版本可能存在的定义)

#####################################
# userdebug_or_eng(rules)
# 
# 仅在 userdebug 和 eng 构建中包含 rules
# User 构建中这些规则被忽略
#
# 注意:target_build_variant 是 m4 处理时传入的变量

define(`userdebug_or_eng',
    ifelse(target_build_variant, `user', , $1)
)

# 类似的条件宏:
# eng_only(rules)       - 仅 eng 版本
# user_only(rules)      - 仅 user 版本
# userdebug_only(rules) - 仅 userdebug 版本

m4 语法解释

  • ifelse(A, B, C, D) - 如果 A 等于 B,返回 C,否则返回 D
  • ifelse(target_build_variant, user', , 1)\` - 如果构建类型是 user,返回空,否则返回第一个参数 1
8.7.3 m4 宏处理示例

原始策略文件

sepolicy 复制代码
# system/sepolicy/private/shell.te

userdebug_or_eng(`
    get_prop(shell, bluetooth_prop)
    get_prop(shell, debug_prop)
')

m4 处理过程

bash 复制代码
# User 版本编译
m4 -Duserdebug_or_eng=ignore_arg shell.te

# 结果:userdebug_or_eng 中的内容被移除
# (空输出)

# userdebug/eng 版本编译
m4 -Duserdebug_or_eng=echo_arg shell.te

# 结果:userdebug_or_eng 中的内容被保留
get_prop(shell, bluetooth_prop)
get_prop(shell, debug_prop)
8.7.4 构建系统中的相关文件
文件路径 作用
system/sepolicy/Android.mk 定义构建规则,注入 m4 宏
system/sepolicy/public/te_macros 定义常用的 SELinux 宏模板
build/core/main.mk 设置 TARGET_BUILD_VARIANT
device/<厂商>/<设备>/BoardConfig.mk 设备特定的 sepolicy 配置
8.7.5 查看 te_macros 中的相关宏
m4 复制代码
# system/sepolicy/public/te_macros

#####################################
# userdebug_or_eng(rules)
# 
# 仅在 userdebug 和 eng 构建中包含 rules
# User 构建中这些规则被忽略
#
# 这个宏实际上由构建系统动态定义
# 不是在 te_macros 文件中硬编码

# 类似的条件宏还有:
# - eng_only(rules)      - 仅 eng 版本
# - user_only(rules)     - 仅 user 版本  
# - userdebug_only(rules) - 仅 userdebug 版本
8.7.6 自定义条件宏

厂商可以定义自己的条件宏:

makefile 复制代码
# device/<厂商>/<设备>/BoardConfig.mk

# 自定义 m4 宏定义
BOARD_SEPOLICY_M4DEFS += \
    my_custom_debug_rule=echo_arg

# 或在特定条件下定义
ifeq ($(MY_CUSTOM_FEATURE),true)
    BOARD_SEPOLICY_M4DEFS += \
        -Dmy_feature_enabled=true
endif
sepolicy 复制代码
# device/<厂商>/<设备>/sepolicy/private/custom.te

my_custom_debug_rule(`
    allow shell custom_prop:file { read open };
')
8.7.7 验证构建类型
bash 复制代码
# 方法一:检查 ro.build.type 属性
$ adb shell getprop ro.build.type
userdebug  # 或 user / eng

# 方法二:检查 ro.debuggable 属性
$ adb shell getprop ro.debuggable
1  # userdebug/eng
0  # user

# 方法三:检查编译输出
# 在源码编译输出中查看
$ cat out/target/product/<device>/obj/ETC/sepolicy_intermediates/policy.conf
# 搜索 get_prop(shell, bluetooth_prop) 是否存在

# 方法四:在设备上查看实际策略
$ adb shell cat /sys/fs/selinux/policy | strings | grep "bluetooth_prop"
8.7.8 完整的构建类型与权限关系图
复制代码
┌─────────────────────────────────────────────────────────────┐
│        构建类型 → SELinux 策略 → 属性权限 关系图            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  构建配置                                                   │
│  ┌─────────────────────────────────────────────┐           │
│  │ TARGET_BUILD_VARIANT = userdebug            │           │
│  │ (定义在 build/core/main.mk 或 lunch 选择)    │           │
│  └─────────────────────────────────────────────┘           │
│                         │                                   │
│                         ▼                                   │
│  ┌─────────────────────────────────────────────┐           │
│  │ Android.mk 构建规则                          │           │
│  │ ifeq ($(TARGET_BUILD_VARIANT),user)          │           │
│  │     M4DEFS += -Duserdebug_or_eng=ignore      │           │
│  │ else                                         │           │
│  │     M4DEFS += -Duserdebug_or_eng=expand      │           │
│  │ endif                                        │           │
│  └─────────────────────────────────────────────┘           │
│                         │                                   │
│                         ▼                                   │
│  ┌─────────────────────────────────────────────┐           │
│  │ m4 宏处理器编译 *.te 文件                    │           │
│  │ m4 ${M4DEFS} shell.te → shell.te.processed  │           │
│  └─────────────────────────────────────────────┘           │
│                         │                                   │
│         ┌───────────────┴───────────────┐                  │
│         │                               │                  │
│         ▼                               ▼                  │
│  ┌─────────────────┐           ┌─────────────────┐        │
│  │ userdebug/eng   │           │     user        │        │
│  │ 编译结果:      │           │ 编译结果:      │        │
│  │ get_prop(shell, │           │ (无此规则)      │        │
│  │   bluetooth_prop)│           │                 │        │
│  └─────────────────┘           └─────────────────┘        │
│         │                               │                  │
│         ▼                               ▼                  │
│  ┌─────────────────┐           ┌─────────────────┐        │
│  │ adb shell 可以  │           │ adb shell 无法  │        │
│  │ 读取 bluetooth  │           │ 读取 bluetooth  │        │
│  │ 属性            │           │ 属性(返回空)  │        │
│  └─────────────────┘           └─────────────────┘        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

以下是一些在 AOSP 中实际存在的条件权限规则:

sepolicy 复制代码
# system/sepolicy/private/domain.te

# 所有域在 userdebug/eng 版本获得额外权限
userdebug_or_eng(`
    # 堆分析权限
    can_profile_heap_userdebug_or_eng({
        domain
        -bpfloader
        -init
        -kernel
    })
')

# system/sepolicy/private/shell.te

# shell 域在 userdebug/eng 版本的额外权限
userdebug_or_eng(`
    # 调试文件系统访问
    allow shell debugfs_tracing_debug:file rw_file_perms;
')

# system/sepolicy/private/app.te

# 应用域在 userdebug/eng 版本的额外权限
userdebug_or_eng(`
    # 与调试相关的权限
    allow appdomain zygote:fifo_file write;
')

7.1 构建类型说明

Android 构建类型分为三种:

构建类型 说明 典型用途
eng 工程版本 内部开发、调试
userdebug 调试版本 测试、验证
user 用户版本 正式发布、量产

8.2 属性访问权限对比

属性前缀 eng 版本 userdebug 版本 user 版本
ro.* ✅ 可读 ✅ 可读 ✅ 可读
sys.* ✅ 可读写 ✅ 可读写 ✅ 可读写
persist.* ✅ 可读写 ✅ 可读写 ✅ 可读写
debug.* ✅ 可读写 ✅ 可读写 ⚠️ 部分受限(SELinux)
log.tag.* ✅ 可读写 ✅ 可读写 ⚠️ 部分受限

注意persist.bluetooth.disableabsvol 使用 persist. 前缀,在所有构建类型中都可以正常访问。

7.3 构建配置文件

makefile 复制代码
# build/core/main.mk

# 构建类型定义
ifneq (,$(filter userdebug eng,$(TARGET_BUILD_VARIANT)))
  # Debug 构建配置
  ADDITIONAL_DEFAULT_PROPERTIES += \
      ro.debuggable=1 \
      ro.adb.secure=0 \
      persist.sys.usb.config=adb
      
  # 启用调试属性
  ADDITIONAL_DEFAULT_PROPERTIES += \
      debug.sf.nobootanimation=0
else
  # User 构建配置
  ADDITIONAL_DEFAULT_PROPERTIES += \
      ro.debuggable=0 \
      ro.adb.secure=1 \
      persist.sys.usb.config=none
      
  # 禁用调试属性访问
endif

九、解决方案

9.1 方案一:使用开发者选项设置

对于蓝牙绝对音量等需要在 User 版本设置的属性,推荐通过开发者选项 UI 进行设置:

java 复制代码
// 开发者选项中设置蓝牙绝对音量
// 设置 → 系统 → 开发者选项 → 禁用绝对音量

// 代码中读取状态
boolean isAbsoluteVolumeDisabled = SystemProperties.getBoolean(
    "persist.bluetooth.disableabsvol", 
    false  // 默认启用绝对音量
);

9.2 方案二:使用 Settings 系统

对于需要持久化的配置,推荐使用 Settings 系统:

java 复制代码
// 使用 Settings.Global 存储配置

// 设置配置值
Settings.Global.putInt(context.getContentResolver(), 
                       "custom_bluetooth_config", 
                       1);

// 获取配置值
int value = Settings.Global.getInt(context.getContentResolver(), 
                                   "custom_bluetooth_config", 
                                   1); // 默认值

9.3 方案三:定义自定义系统属性

在系统源码中添加自定义属性,绕过 debug 前缀限制:

步骤 1:定义属性
makefile 复制代码
# device/<company>/<device>/system.prop

# 自定义蓝牙属性(不使用 debug 前缀)
ro.bluetooth.absolute_volume_enabled=1
persist.bluetooth.absolute_volume_enabled=1
步骤 2:添加 SELinux 上下文
plaintext 复制代码
# device/<company>/<device>/sepolicy/property_contexts

# 自定义蓝牙属性
bluetooth.absolute_volume_enabled  u:object_r:bluetooth_prop:s0
步骤 3:添加 SELinux 权限
sepolicy 复制代码
# device/<company>/<device>/sepolicy/system_server.te

# 允许 system_server 读取自定义蓝牙属性
allow system_server bluetooth_prop:file { read open };
步骤 4:修改源码使用自定义属性
java 复制代码
// frameworks/base/services/core/java/com/android/server/audio/AudioService.java

private boolean shouldEnableAvrcpAbsoluteVolume() {
    // 使用自定义属性替代 debug 属性
    String propValue = SystemProperties.get("bluetooth.absolute_volume_enabled", "1");
    return propValue.equals("1");
}

9.4 方案四:修改系统源码(仅限系统定制)

如果需要修改系统行为,可以修改源码中的属性获取逻辑:

cpp 复制代码
// system/core/init/property_service.cpp

// 修改属性限制检查(不推荐,仅用于特殊需求)
static bool IsRestrictedProperty(const std::string& name) {
    // 对于特定属性,在 user 版本也允许访问
    static const char* allowed_debug_properties[] = {
        "debug.bluetooth.absolute_volume",
        // 添加其他需要放开的属性
    };
    
    for (const char* prop : allowed_debug_properties) {
        if (name == prop) {
            return false; // 不受限
        }
    }
    
    // 其他属性保持原有逻辑
    // ...
}

9.5 方案五:使用 DeviceConfig

Android 11+ 提供了 Feature Flags 机制:

java 复制代码
// 使用 DeviceConfig 替代系统属性

// 设置配置值
DeviceConfig.setProperty(
    DeviceConfig.NAMESPACE_BLUETOOTH,
    "absolute_volume_enabled",
    "true",
    false  // 不立即同步
);

// 获取配置值
boolean enabled = DeviceConfig.getBoolean(
    DeviceConfig.NAMESPACE_BLUETOOTH,
    "absolute_volume_enabled",
    true  // 默认值
);

十、实际案例分析

10.1 案例一:蓝牙绝对音量开关设置

问题描述

用户希望在 User 版本设备上禁用蓝牙绝对音量,以解决某些蓝牙设备音量调节异常的问题。

解决方案

方案一:开发者选项(推荐)

java 复制代码
// 通过开发者选项 UI 设置
// 设置 → 系统 → 开发者选项 → 禁用绝对音量

// 该开关对应的代码逻辑:
// Settings 开发者选项设置 persist.bluetooth.disableabsvol 属性

方案二:代码中检测和提示

java 复制代码
/**
 * 检查绝对音量状态并提供用户引导
 */
public class BluetoothVolumeHelper {
    
    /**
     * 检查绝对音量是否启用
     */
    public static boolean isAbsoluteVolumeEnabled() {
        // persist.bluetooth.disableabsvol 为 true 时表示禁用
        return !SystemProperties.getBoolean("persist.bluetooth.disableabsvol", false);
    }
    
    /**
     * 引导用户到开发者选项设置
     */
    public static void openDeveloperOptions(Context context) {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS);
        context.startActivity(intent);
    }
    
    /**
     * 显示绝对音量状态对话框
     */
    public static void showVolumeStatusDialog(Context context) {
        boolean enabled = isAbsoluteVolumeEnabled();
        String message = enabled 
            ? "蓝牙绝对音量已启用,音量将与蓝牙设备同步" 
            : "蓝牙绝对音量已禁用,请手动调节蓝牙设备音量";
        
        new AlertDialog.Builder(context)
            .setTitle("蓝牙绝对音量")
            .setMessage(message)
            .setPositiveButton("去设置", (d, w) -> openDeveloperOptions(context))
            .setNegativeButton("取消", null)
            .show();
    }
}
注意事项
  1. 设置 persist.bluetooth.disableabsvol 后需要重启设备才能生效
  2. 部分厂商 ROM 可能修改了此属性的名称或行为
  3. 禁用绝对音量后,需要手动调节蓝牙设备的物理音量

10.2 案例二:调试日志开关失效

问题描述
bash 复制代码
# 在 User 版本执行
adb shell setprop log.tag.Bluetooth VERBOSE
adb shell getprop log.tag.Bluetooth
# 返回空值
原因分析

log.tag.* 属性在 User 版本受到 SELinux 策略限制:

sepolicy 复制代码
# system/sepolicy/private/log_tag.te
neverallow { domain -init } log_tag_prop:file { write };
解决方案
java 复制代码
// 使用 android.util.Log 的 API 控制
if (Build.IS_DEBUGGABLE) {
    // 仅在可调试版本设置
    Log.d("Bluetooth", "Debug logging enabled");
}

10.3 案例三:USB 配置无法修改

问题描述
bash 复制代码
# User 版本尝试修改 USB 配置
adb shell setprop persist.sys.usb.config adb
# 失败:Permission denied
原因分析

persist.sys.usb.config 涉及设备安全,在 User 版本有严格限制:

sepolicy 复制代码
# 仅 init 和特定服务可以设置
neverallow { domain -init -system_server } persist_usb_prop:property_service set;
解决方案
java 复制代码
// 使用 UsbManager API(需要系统权限)
UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
// 通过系统 API 配置 USB 模式

十一、最佳实践建议

10.1 属性使用原则

原则 说明
避免使用 debug. 前缀 User 版本受限
使用 persist. 存储持久配置 重启后保留
使用 ro. 存储只读配置 防止意外修改
考虑 Settings 替代方案 更灵活且不受构建类型限制

10.2 代码兼容性处理

java 复制代码
/**
 * 安全获取系统属性的工具类
 */
public class PropertyUtils {
    
    /**
     * 安全获取属性值
     * @param key 属性名称
     * @param defaultValue 默认值
     * @return 属性值
     */
    public static String getProperty(String key, String defaultValue) {
        try {
            String value = SystemProperties.get(key, defaultValue);
            
            // 检查是否为空(可能因权限限制返回空)
            if (TextUtils.isEmpty(value)) {
                // 尝试从 Settings 获取
                value = getFromSettings(key, defaultValue);
            }
            
            return value;
        } catch (Exception e) {
            Log.w("PropertyUtils", "Failed to get property: " + key, e);
            return defaultValue;
        }
    }
    
    /**
     * 从 Settings 获取配置(备用方案)
     */
    private static String getFromSettings(String key, String defaultValue) {
        // 实现从 Settings 获取的逻辑
        // ...
        return defaultValue;
    }
    
    /**
     * 检查属性是否可访问
     */
    public static boolean isPropertyAccessible(String key) {
        String value = SystemProperties.get(key, "");
        return !TextUtils.isEmpty(value) || 
               !Build.TYPE.equals("user") || 
               !key.startsWith("debug.");
    }
}

10.3 调试与验证

bash 复制代码
# 检查当前构建类型
adb shell getprop ro.build.type

# 检查属性 SELinux 上下文
adb shell ls -Z /dev/__properties__

# 检查属性是否存在
adb shell getprop | grep debug.bluetooth

# 使用 dmesg 查看属性访问日志
adb shell dmesg | grep -i property

十三、总结

本文从 Android 系统源码层面深入分析了 getprop 属性在不同构建版本的表现差异:

核心发现

  1. property_service.cpp 不限制属性读取

    • 属性读取需要经过 SELinux 权限检查
    • 该文件主要负责属性设置权限的验证
    • 真正的读取限制来源于 SELinux 策略
  2. 蓝牙绝对音量属性

    • 正确的属性名是 persist.bluetooth.disableabsvol
    • 在 Android 14/15 User 版本中,adb shell 无法读取
    • 原因:bluetooth_prop 类型未被授权给 shell
  3. 属性读取权限的核心机制

    • SELinux 类型标签 :每个属性都有对应的 SELinux 类型(如 bluetooth_prop
    • 域权限控制 :每个进程域有明确的 get_prop 权限列表
    • neverallow 规则:禁止特定域访问特定属性类型

关键对比

场景 shell 域 system_server 域 bluetooth 域
读取 persist.bluetooth.* ❌ 受限 ✅ 允许 ✅ 允许
读取 debug.* ❌ 受限 ⚠️ userdebug/eng ⚠️ userdebug/eng
读取 sys.* ✅ 允许 ✅ 允许 ✅ 允许
读取 ro.* ✅ 允许 ✅ 允许 ✅ 允许

解决方案总结

开发者类型 推荐方案
应用开发者 使用系统 API,不直接依赖属性
系统开发者 system_serverbluetooth 域中读取
厂商定制 修改 SELinux 策略添加 get_prop(shell, bluetooth_prop)
调试需求 使用 userdebug/eng 版本或 root 权限

Android 版本演进

Android 版本 属性读取权限变化
Android 10 及以前 SELinux 检查相对宽松
Android 11-13 开始收紧部分属性访问
Android 14/15 严格执行 SELinux 属性读取检查,shell 域受限更多

最佳实践

java 复制代码
// 应用层:不要直接读取属性,使用系统 API
// ❌ 错误方式
String value = SystemProperties.get("persist.bluetooth.disableabsvol");

// ✅ 正确方式
// 1. 使用 AudioManager 等系统服务 API
// 2. 监听系统广播获取状态变化
// 3. 通过 ContentProvider 或 AIDL 获取系统服务状态
bash 复制代码
# 系统开发调试:检查 SELinux 拒绝日志
adb shell dmesg | grep -i "avc.*bluetooth.*prop"

# 临时关闭 SELinux(仅用于调试)
adb shell setenforce 0

本文从 Android 系统源码层面深入分析了 getprop 属性在不同构建版本的表现差异:

核心发现

  1. property_service.cpp 不限制属性读取

    • 属性读取直接从共享内存获取,无权限过滤
    • 该文件主要负责属性设置权限的验证
    • 真正的限制来源于 SELinux 策略和构建配置
  2. 蓝牙绝对音量属性

    • 正确的属性名是 persist.bluetooth.disableabsvol
    • 使用 persist. 前缀,在所有构建类型中都可访问
    • User 版本可以正常读取,设置需要通过开发者选项或 root 权限
  3. 属性限制的真实原因

    • SELinux 策略:控制特定域对特定属性的访问权限
    • 构建配置:某些属性只在 debug 构建中定义
    • 属性不存在:部分属性未被定义或加载

属性使用建议

场景 推荐方案
需要持久化的配置 使用 persist. 前缀属性
调试开关 使用开发者选项 UI 设置
应用配置 使用 Settings 系统
运行时状态 使用 sys. 前缀属性
只读配置 使用 ro. 前缀属性

蓝牙绝对音量属性要点

bash 复制代码
# 属性名称
persist.bluetooth.disableabsvol

# 读取状态
adb shell getprop persist.bluetooth.disableabsvol

# 设置方式
# 1. 开发者选项 UI(推荐)
# 2. root 权限下 setprop(需重启)
# 3. 系统签名应用内设置

# 值的含义
true  = 禁用绝对音量
false 或不存在 = 启用绝对音量(默认)

参考资料


附录:常见问题解答

Q1: 为什么我在 system/sepolicy/Android.mk 中找不到 userdebug_or_eng 宏的定义?

:这是正常现象。

您看到的代码可能只是:

makefile 复制代码
ifdef BOARD_SEPOLICY_M4DEFS
LOCAL_ADDITIONAL_M4DEFS := $(addprefix -D, $(BOARD_SEPOLICY_M4DEFS))
else
LOCAL_ADDITIONAL_M4DEFS :=
endif

这段代码的作用是处理 来自其他地方的宏定义,而不是定义 userdebug_or_eng 宏本身。

userdebug_or_eng 宏的实际定义位置取决于 Android 版本:

Android 版本 可能的定义位置
Android 8.x - 10 system/sepolicy/public/te_macros 文件中
Android 11+ build/core/config.mkbuild/core/main.mk 或 Soong 构建系统中

如何验证

bash 复制代码
# 在 AOSP 源码中搜索
grep -r "userdebug_or_eng" build/core/ system/sepolicy/

# 查看 te_macros 文件
cat system/sepolicy/public/te_macros | grep -B2 -A10 "userdebug_or_eng"

Q2: 为什么 userdebug 版本能读取 bluetooth_prop,而 user 版本不能?

:这取决于厂商的 SELinux 策略定制。

标准 AOSP 情况

  • shell 域在所有版本 (user/userdebug/eng)都没有 bluetooth_prop 的读取权限
  • 这是由 system/sepolicy/public/shell.te 中的权限定义决定的

厂商定制情况

如果您的 userdebug 版本能读取 persist.bluetooth.*,很可能是厂商在设备特定的 SELinux 策略中添加了条件权限:

sepolicy 复制代码
# device/<厂商>/<设备>/sepolicy/private/shell.te

# 厂商添加的条件权限
userdebug_or_eng(`
    get_prop(shell, bluetooth_prop)
    get_prop(shell, debug_prop)
    get_prop(shell, nfc_prop)
')

userdebug_or_eng 宏的定义 (AOSP 标准,位于 system/sepolicy/public/te_macros):

m4 复制代码
define(`userdebug_or_eng', ifelse(target_build_variant, `eng', $1, ifelse(target_build_variant, `userdebug', $1)))

宏展开效果

构建类型 userdebug_or_eng(...) 展开结果
user 空(内容被移除)
userdebug 展开为原内容
eng 展开为原内容

验证方法

bash 复制代码
# 检查构建类型
adb shell getprop ro.build.type

# 检查 SELinux 权限(需要 root)
adb root
adb shell sesearch -A -s shell -t bluetooth_prop -c file

# userdebug 版本(如果有厂商定制)输出:
# allow shell bluetooth_prop:file { read open };

# user 版本输出:
# (无结果)

# 查看 SELinux 拒绝日志
adb shell "dmesg | grep -i 'avc.*bluetooth.*prop'"

Q3: 如何让 user 版本的 shell 也能读取 bluetooth_prop?

:有以下几种方案:

方案一:修改 SELinux 策略(不推荐,降低安全性)

sepolicy 复制代码
# device/<厂商>/<设备>/sepolicy/private/shell.te

# 直接添加权限(绕过 userdebug_or_eng 条件)
get_prop(shell, bluetooth_prop)

方案二:将属性映射到 shell 可读的类型

plaintext 复制代码
# device/<厂商>/<设备>/sepolicy/private/property_contexts

# 将特定属性映射到 system_prop(shell 默认可读)
persist.bluetooth.disableabsvol    u:object_r:system_prop:s0 exact bool

方案三:通过系统 API 访问(推荐)

应用层不直接读取属性,而是通过系统服务提供的 API 获取状态。

相关推荐
郝学胜-神的一滴1 小时前
Qt 高级开发 019:从零定制登录窗口按钮、Logo 样式与交互悬浮效果
开发语言·c++·qt·程序人生·交互·用户界面
石工记1 小时前
CTO如何落地AI?从0到1的实战路径
人工智能·python·django·flask·numpy·pandas·pyqt
星夜夏空991 小时前
FreeRTOS学习(5)——内存映射
开发语言·学习
wuxinyan1232 小时前
工业级大模型学习之路031:Streamlit 高级功能多会话管理和知识库管理
python·学习·智能体
llilay2 小时前
企业级FastAPI后端模板搭建(三)整合日志Log
数据库·python·fastapi
yujunl2 小时前
resx文件上具有 Web 标记
开发语言
catchadmin2 小时前
免费可商用 PHP 管理后台 CatchAdmin V5.3.1 发布 后台打包直降 5s 内
开发语言·php
小江的记录本2 小时前
【Spring AI】Spring AI中RAG误触发与系统提示词泄露问题解决方案(完整版+代码方案)
java·人工智能·spring boot·后端·python·spring·面试
YY&DS2 小时前
Qt Designer 自定义控件已提升后,如何修改提升类
开发语言·qt