去掉安卓的“读取已安装应用列表”,隐私合规

vivo:https://dev.vivo.com.cn/documentCenter/doc/747

XML 复制代码
<meta-data
     android:name="oem_installed_apps_runtime_permission_enable"
     android:value="true"/>

小米:https://dev.mi.com/xiaomihyperos/documentation/detail?pId=1619

XML 复制代码
<meta-data
     android:name="do_not_need_get_installed_apps"
     android:value="true"/>

通过上述方案适配,小米和vivo设备基本就不会弹出了。不过oppo和华为(mate 30 pro) 还是会弹出

最终方案:

在工程AndroidManifest.xml里加上以下代码:

XML 复制代码
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" 

tools:node="remove" />

<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" 

tools:ignore="QueryAllPackagesPermission" 

tools:node="remove" />

在混淆文件proguard-rules.pro里加上以下代码:

XML 复制代码
-assumenosideeffects class android.content.pm.PackageManager {
    public java.util.List getInstalledPackages(int) return null;
    public java.util.List getInstalledApplications(int) return null;
}

即可解决oppo和华为设备的 "读取已安装应用列表" 弹窗

最最终方案:

与隐私合规检测三方进行沟通,机审存在误判

XML 复制代码
截图内给到的日志1是:

```
2026-03-30 15:24:08.416  2524-3019  TelephonyPermissions  com.android.phone
E Exception caught obtaining package info for package null 
android.content.pm.PackageManager$NameNotFoundException
    at android.app.ApplicationPackageManager.getApplicationInfoAsUser(ApplicationPackageManager.java:452)
    at android.content.pm.PackageManager.getApplicationInfoAsUser(PackageManager.java:4759)
    at com.android.internal.telephony.TelephonyPermissions.reportAccessDeniedToReadIdentifiers(TelephonyPermissions.java:
    at com.android.internal.telephony.TelephonyPermissions.checkPrivilegedReadPermissionOrCarrierPrivilegePermission(Tele
    at com.android.internal.telephony.TelephonyPermissions.checkCallingOrSelfReadDeviceIdentifiers(TelephonyPermissions.j
    at com.android.internal.telephony.PhoneSubInfoController.callPhoneMethodForPhoneIdWithReadDeviceIdentifiersCheck(Phon
    at com.android.internal.telephony.PhoneSubInfoController.getDeviceIdForPhone(PhoneSubInfoController.java:74)
    at com.android.internal.telephony.PhoneSubInfoController.getDeviceIdWithFeature(PhoneSubInfoController.java:68)
    at com.android.internal.telephony.PhoneSubInfoController.getDeviceId(PhoneSubInfoController.java:64)
    at com.android.internal.telephony.IPhoneSubInfo$Stub.onTransact(IPhoneSubInfo.java:457)
    at android.os.Binder.execTransactInternal(Binder.java:1184)
    at android.os.Binder.execTransact(Binder.java:1143)
```

然后 上面的 d2f34955c580bff6d5c54bf3fb821cda.xlsx 给到的日志2是 :

```
	at android.app.ApplicationPackageManager.getInstalledPackagesAsUser()
	at android.app.ApplicationPackageManager.getInstalledPackages(ApplicationPackageManager.java:735)
```

上述反馈日志 1 是具体可以本地复线的场景,从日志 1 内也能看出缺少关键的 getInstalledPackages 方法,我们已经仔细分析 sdk 内的逻辑,在设置 Main.setConfig("pkglist", "1"); 方法关闭应用列表采集之后,模块内就不会触发 getInstalledPackages 的调用。

还有获取设备已安装应用列表的系统标准 API 是 getInstalledPackages 或 getInstalledApplications。而本次堆栈中触发的是 getApplicationInfoAsUser ,这两个接口有着本质区别:后者仅用于查询单一指定包名的应用信息,完全不具备"获取列表"的功能。

从源码内可以看到触发日志的方法是
```
callingPackageInfo = context.getPackageManager().getApplicationInfoAsUser(callingPackage, 0, UserHandle.getUserHandleForUid(uid));
```
系统传入的参数是 callingPackage(即当前正在运行的 App 自身的包名)。系统调用此接口的唯一目的,是为了在第 442 行核对当前 App 的 targetSdkVersion 是否小于 Android 10 (Q):
```
if (callingPackageInfo != null && (callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q))
```


这是 Android 系统底层为了兼容老版本 App 的权限逻辑,系统自身发起的一次"查询当前 App 自身的 targetSdkVersion"的操作,完全不涉及对外部其他应用的扫描,更未触碰任何"应用列表"隐私数据。建议将此源码逻辑反馈给审核机构,以解除自动化工具的误判。


日志对应的源码:

https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/telephony/common/com/android/internal/telephony/TelephonyPermissions.java;l=417?q=reportAccessDeniedToReadIdentifiers

```
/**
     * Reports a failure when the app with the given pid/uid cannot access the requested identifier.
     *
     * @returns false if the caller is targeting pre-Q and does have the READ_PHONE_STATE
     * permission or carrier privileges.
     * @throws SecurityException if the caller does not meet any of the requirements for the
     *                           requested identifier and is targeting Q or is targeting pre-Q
     *                           and does not have the READ_PHONE_STATE permission or carrier
     *                           privileges.
     */
    private static boolean reportAccessDeniedToReadIdentifiers(Context context, int subId, int pid,
            int uid, String callingPackage, String message) {
        ApplicationInfo callingPackageInfo = null;
        try {
            callingPackageInfo = context.getPackageManager().getApplicationInfoAsUser(
                    callingPackage, 0, UserHandle.getUserHandleForUid(uid));
        } catch (PackageManager.NameNotFoundException e) {
            // If the application info for the calling package could not be found then assume the
            // calling app is a non-preinstalled app to detect any issues with the check
            Log.e(LOG_TAG, "Exception caught obtaining package info for package " + callingPackage,
                    e);
        }
        // The current package should only be reported in StatsLog if it has not previously been
        // reported for the currently invoked device identifier method.
        boolean packageReported = sReportedDeviceIDPackages.containsKey(callingPackage);
        if (!packageReported || !sReportedDeviceIDPackages.get(callingPackage).contains(
                message)) {
            Set invokedMethods;
            if (!packageReported) {
                invokedMethods = new HashSet<String>();
                sReportedDeviceIDPackages.put(callingPackage, invokedMethods);
            } else {
                invokedMethods = sReportedDeviceIDPackages.get(callingPackage);
            }
            invokedMethods.add(message);
            TelephonyCommonStatsLog.write(TelephonyCommonStatsLog.DEVICE_IDENTIFIER_ACCESS_DENIED,
                    callingPackage, message, /* isPreinstalled= */ false, false);
        }
        Log.w(LOG_TAG, "reportAccessDeniedToReadIdentifiers:" + callingPackage + ":" + message + ":"
                + subId);
        // if the target SDK is pre-Q then check if the calling package would have previously
        // had access to device identifiers.
        if (callingPackageInfo != null && (
                callingPackageInfo.targetSdkVersion < Build.VERSION_CODES.Q)) {
            if (context.checkPermission(
                    android.Manifest.permission.READ_PHONE_STATE,
                    pid,
                    uid) == PackageManager.PERMISSION_GRANTED) {
                return false;
            }
            if (checkCarrierPrivilegeForSubId(context, subId)) {
                return false;
            }
        }
        throwSecurityExceptionAsUidDoesNotHaveAccess(message, uid);
        return true;
    }
```
相关推荐
idealzouhu19 小时前
【NDK开发】Android NDK 原生构建:ndk-build 与 CMake
android·ndk
shuangrenlong19 小时前
android studio突然一直importing卡住
android·ide·android studio
码云数智-大飞19 小时前
类型系统攻防战:PHP混合类型与联合类型对隐式类型转换漏洞的防御策略
android
寒秋花开曾相惜19 小时前
(学习笔记)4.2 逻辑设计和硬件控制语言HCL(4.2.3 字级的组合电路和HCL整数表达式)
android·网络·数据结构·笔记·学习
zhangphil20 小时前
Android sql查媒体数据封装room Dao构造AndroidViewModel,RecyclerView宫格展示,Kotlin
android·kotlin
pengyu21 小时前
【Kotlin 协程修仙录 · 筑基境 · 中阶】 | 身份证与通行证:CoroutineContext 的深度解剖
android·kotlin
夏沫琅琊1 天前
android 短信读取与导出技术
android·kotlin
dalancon1 天前
Android LMKD 服务
android
迪普阳光开朗很健康1 天前
告别繁琐!用ApkInfoQuick快速提取APK关键信息
android·rust·react
深度智能Ai1 天前
GPT Image 2 图片生成 API 接口对接文档
android·gpt