【问题标题】:Is there any way to detect to which apps I can/can't reach their "app-info" screen?有什么方法可以检测到哪些应用程序我可以/无法访问他们的“应用程序信息”屏幕?
【发布时间】:2021-01-11 18:01:19
【问题描述】:

背景

您可以使用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”(主要组件”)。也许更多。

向谷歌举报后,我是told

com.google.android.ext.services 是主线模块,因此设置不提供详细的应用信息。

我尝试过的

我尝试查看 PackageInfo 和 ApplicationInfo 的各个字段和功能。

我找到了“isApex”,但它似乎总是错误的,the docs 根本无助于理解它是什么(“该包是否为 APEX 包”)。编辑:如果我检查 API 30,它总是错误的。在 API 29 上,它实际上有时设置为 true。举报here

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

这是获取它的代码:

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

问题

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

【问题讨论】:

  • 看来coreApp 不是您要查找的属性。在我的设备上,"com.google.android.modulemetadata" 对于coreApp 的值为false
  • 你见过this吗?有一个关于 APEX 文件的内部结构及其与包管理器的关系的讨论,这可能是密切相关的。
  • @Cheticamp APEX 文件是否可能用于系统模块?我不确定我是否理解此链接所说的内容。而且我已经测试了“isApex”,它总是错误的(正如我所写的)......
  • 您收到的回复中的关键词可能是“模块”而不是“主线”。请参阅 API 9 中新增的 PackageManager#getInstalledModules(int)
  • 这正是我所遵循的路径。我没有检查“文件”应用程序,但是,在 API 30 的股票模拟器上,app-info 适用于已安装的包,它们要么在模块中没有条目,要么在模块中有 isHidden 为 false 的条目.我认为这是正确的轨道。

标签: android android-applicationinfo package-info


【解决方案1】:

startActivity() 的调用失败。理想情况下,我们会将该调用追溯到 Android 系统,并找出它为什么无法获取一些可能导致修复的信息。取而代之的是,我提出以下作为可能的解决方案。

我假设所有已安装的软件包都可以显示“应用程序信息”屏幕,除非该软件包是 hidden 的模块。

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

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

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)
    }
}

【讨论】:

  • “Files”应用程序(而不是“Files by Google”,它是一个不同的应用程序)只是被标记为一个模块,但没有被隐藏(并且是唯一一个这样的应用程序,在所有这些中)我有)。关于示例,您忘记将 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'' 添加到 gradle。非隐藏模块的部分不应被评论,因为它应该仍然是安全的(如“文件”应用程序)。我喜欢你最终测试它的方式(尽管你在 POC 中不需要协程,因为你可以很容易地使用 Handler 代替)。 associate 有点危险:可空字段
  • 奇怪的是,我在您的 POC 中没有看到“文件”应用程序。我稍后会再次检查。 associate 是否可以仅处理不可为空的包名称?我知道这与主题无关。只是想知道......
  • @androiddeveloper 查看有关“文件”的更新应用。
  • 哦,现在看起来不错。不知道我以前是怎么错过的。还具有“com.google.android.permissioncontroller”作为未隐藏且确实可以打开的模块。你没有回答我关于 associate 的问题。你知道ModuleInfopackageName 是否有任何理由可以为空(在文档中)? associate不应该跳过这种奇怪的情况吗?
  • @androiddeveloper ModuleInfo#getPackageName() 可以返回 null,但我不确定为什么会这样,或者即使它是应用程序会看到 IRL 的东西。至于“关联”,可以添加一个过滤器来清除空包名,以防出现空包名。
【解决方案2】:

“主线模块”的含义可以在这里解释:Modular System Components
如果这些应该是唯一具有这种(非常见应用程序类似)行为的,可以通过它们的包名来识别它们。这些将是要过滤的所有包名称:

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(如文档所示),因此它不能可以用来识别它们,但是包名是一个可靠的标准,可以使用。
字符串比较可能不是预期的答案,但有更好的标准吗?

【讨论】:

  • 你是如何得到这份名单的?使用在另一个答案中找到的getInstalledModules?如果是这样,请注意您的列表包含一些您实际上可以访问其应用程序信息屏幕的应用程序(如图所示),并且就像我在那里写的那样,您可能应该通过检查模块是否隐藏来过滤掉。如果您的答案不同,请解释原因。否则,就像重复另一个人的答案一样。
  • 我没有重复任何人的答案,你是怎么得出这个结论的??这让我感到抱歉,我什至已经回答了。我几乎没有与文档争论过,它列出了这些“模块化系统组件”的所有包名称。即使有些人可能有那个应用程序页面,也只需将它们从列表中删除,直到只剩下那些没有的。具有应用信息页面的那些仍然可能与普通 APK 的行为不同。
  • 我问过关于通过代码检查的问题。你提供了一个列表,所以我问你从哪里得到它,这样我也可以得到它(通过代码)。其他设备可能比文档中的列表更多,也可能更少。对不起,你对此感到不安。我不是故意让你难过的。文档很重要,但我问的是代码。
  • 至于isApex,我注意到我在Android API 30上找到的所有应用程序都设置为false,但对于API 29上的某些应用程序它设置为true,所以虽然它可能是 API 29 的可靠方式(我不确定),但我不认为它适用于 API 30。
猜你喜欢
  • 1970-01-01
  • 2014-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-06
  • 2012-02-25
  • 2010-10-05
  • 2012-07-09
相关资源
最近更新 更多