有什么方法可以检测哪些应用程序我 can/can 没有到达他们的 "app-info" 屏幕吗?

Is there any way to detect to which apps I can/can't reach their "app-info" screen?

背景

您可以使用 PackageManager.getInstalledPackages 获取已安装应用的列表。

并且,您可以通过以下方式访问每个应用程序的应用程序信息屏幕:

val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse("package:$appPackageName"))
startActivity(intent)

示例:

问题

事实是,我注意到(在有人告诉我之后)对于某些应用程序,您无法访问其应用程序信息屏幕。这些应用程序的此类包名称示例:“com.google.android.ext.services”(“Android 服务库”)、“com.google.mainline.tememetry”(“支持组件”)、com.google.android.modulemetadata”(主要成分”) 。也许更多。

向Google举报后,我told:

com.google.android.ext.services is mainline module, so Settings doesn't provide detail app info for it.

我试过的

我尝试查看 PackageInfo 和 ApplicationInfo 的各个字段和函数。

我找到了“isApex”,但它似乎总是错误的,the docs don't help about understanding what it is, at all ("Whether the package is an APEX package") . EDIT: it's always false if I check on API 30. On API 29 it's actually sometimes set to true. Reported here

我还发现了一个名为“coreApp”的私有布尔字段(我可以通过反射到达),确实有时它是真的,但并不总是当它是真的时,这意味着我无法到达它是应用程序信息屏幕。

这是获取它的代码:

    fun isProbablyCoreApp(packageInfo: PackageInfo): Boolean {
        return try {
            val field = PackageInfo::class.java.getField("coreApp")
            field.getBoolean(packageInfo)
        } catch (e: Throwable) {
            false
        }
    }

问题

  1. “主线模块”是什么意思?它是 OS 的一部分,可以自行更新?与Android10及以上的“项目主线”有关?
  2. 为什么我无法访问它的应用信息?这不是一个真正的应用程序?但如果不是,它怎么会被列为应用程序列表的一部分?
  3. 有什么方法可以检测到安装的应用程序实际上是一个您无法访问其应用程序信息屏幕的模块? OS 的 UI 如何从其列表中过滤掉那些应用程序?
  4. 是否有更多的应用程序无法访问其应用程序信息屏幕?

startActivity() 的调用失败。理想情况下,我们会跟踪该调用进入 Android 系统并弄清楚为什么它无法获得一些可能导致修复的信息。取而代之的是,我建议将以下内容作为 可能的 解决方案。

我假设所有已安装的包都可以显示“apps-info”屏幕,除非该包是 hidden.

的模块

我查看了一个 Android 模拟器 运行 API 30 并检查了上述内容。我不相信这个理论在所有情况下都有效。您提到“文件”应用程序是一个问题。此应用程序显示为模块,但未如您建议的那样出现在已安装应用程序列表中。更新后的代码解决了这个问题。

我整理了一个小应用程序,可以根据上述类别测试是否创建了 apps-info 屏幕。我已将其包含在此处以供进一步查看。代码中的注释解释了该应用程序的工作原理。

class MainActivity : AppCompatActivity() {
    private var mActivitiesStarted = 1 // This activity counts.

    @RequiresApi(Build.VERSION_CODES.Q)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val modulesByPackageName = packageManager.getInstalledModules(MATCH_ALL)
            .asSequence()
            .filter { it.packageName != null } // packageName can be null according to the docs.
            .associate { moduleInfo -> Pair(moduleInfo.packageName, moduleInfo.isHidden) }

        // Comment/uncomment code in different paths to start apps-info activities for the
        // various categories of packages/modules.
        val installedByPackageName = mutableSetOf<String>()
        packageManager.getInstalledPackages(0).forEach {
            installedByPackageName.add(it.packageName)

            when (modulesByPackageName[it.packageName]) {
                true -> {
                    // Package is a module that is hidden. We should not be able to get to apps-info.
                    // Unfortunately, for testing, the activity will start but the apps-info
                    // screen will not display. This condition cannot be tested through a count
                    // of activities.
                    Log.d(
                        "MainActivity",
                        "<<<<Can't reach app-info for ${it.packageName} (hidden module)"
                    )
                    // This will fail to display but the activity will start.
//                    startAppsInfo(it.packageName)
                }
                false -> {
                    // Package is a module that is not hidden. We should be able to get to apps-info.
                    Log.d(
                        "MainActivity",
                        "<<<<Can reach app-info for ${it.packageName} (not hidden module)"
                    )
                    // This should be successful.
                    startAppsInfo(it.packageName)
                    mActivitiesStarted++
                }
                else -> {
                    // Package is not a module. We should be able to get to apps-info.
                    Log.d(
                        "MainActivity",
                        "<<<<Can reach app-info for ${it.packageName} (not module)"
                    )
                    // This should be successful.
                    startAppsInfo(it.packageName)
                    mActivitiesStarted++
                }
            }

        }

        // Look at modules that are not hidden but do not appear in the installed packages list.
        // This should pick up modules like com.google.android.documentsui (Files).
        modulesByPackageName.filter { !it.value && !installedByPackageName.contains(it.key) }
            .forEach {
                Log.d(
                    "MainActivity",
                    "<<<<Can reach app-info for ${it.key} (module but not in installed packages)"
                )
                // This should be successful.
                startAppsInfo(it.key!!)
                mActivitiesStarted++
            }

        // Check that we started the number of activities that we expected. Post to ensure that
        // all activities start and can be counted.
        Handler(Looper.getMainLooper()).post {
            val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
            // getRunningTasks is deprecated, but it still returns tasks for the current app.
            val runningTasks = activityManager.getRunningTasks(Integer.MAX_VALUE)
            val numActivities = runningTasks[0].numActivities
            Log.d(
                "MainActivity",
                "<<<<activitiesStarted=$mActivitiesStarted numActivities=$numActivities"
            )
        }
    }

    private fun startAppsInfo(appPackageName: String) {
        val intent = Intent(
            Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
            Uri.parse("package:$appPackageName")
        )
        startActivity(intent)
    }
}

这里可能会解释“主线模块”的含义:Modular System Components
如果这些应该是唯一具有这种(非常见应用程序相似)行为的,可以通过它们的 package-name 来识别它们。这些将是要筛选的所有 package-name:

com.google.android.adbd
com.android.runtime.release.apex
com.android.captiveportallogin
com.google.android.cellbroadcast
com.android.conscrypt
com.android.resolv
com.android.documentsui
com.android.ext.services
com.google.android.ipsec
com.android.media.swcodec
com.android.media
com.google.android.mediaprovider
com.android.modulemetadata
com.android.networkstack.permissionconfig   
com.android.networkstack
com.google.android.neuralnetworks
com.android.permissioncontroller
com.android.sdkext
com.google.android.os.statsd
com.google.mainline.telemetry
com.google.android.tethering
com.android.tzdata
com.google.android.wifi.apex

目前我没有时间进行概念验证,但信息似乎可靠。
isApex 应该只是其中一些的 true (如文档所示),因此它不能用于识别它们,但 package-name 是一个可靠的标准,可以使用。
String-comparison 可能不是预期的答案,但有没有更好的标准?