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

2

7

Background

You can get a list of installed apps using PackageManager.getInstalledPackages.

And, you can reach the app-info screen of each app via :

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

Example:

enter image description here

The problem

Thing is, I've noticed (after someone told me) that for some apps, you can't reach their app-info screen. Example of such package-names of those apps: "com.google.android.ext.services" ("Android Services Library") , "com.google.mainline.tememetry" ("Support components"), com.google.android.modulemetadata" (Main components") . Maybe more.

After reporting it to Google, I was told:

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

What I've tried

I've tried to look at various fields and functions of PackageInfo and ApplicationInfo.

I've found "isApex", but it seems to be always false, and 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.

I've also found a private boolean field (that I can reach via reflection) called "coreApp" , and indeed it's sometimes true, but it's not always that when it's true, it means I can't reach it's app-info screen.

This is the code to get it:

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

The questions

  1. What does it mean "mainline module" ? It's a part of the OS that gets updated on its own? Related to "project mainline" of Android 10 and above ?
  2. Why couldn't I reach its app-info? It's not a real app? But if not, how come it's listed as a part of the list of apps?
  3. Is there any way to detect that an installed app is in fact a module that you can't reach its app-info screen ? How does the UI of the OS filters out those apps from its list?
  4. Are there more cases of apps that I can't reach their app-info screen?
Chalybite answered 25/9, 2020 at 11:42 Comment(14)
It seems like the coreApp is not the attribute that you are looking for. On my device, "com.google.android.modulemetadata" has the value of false for the coreApp.Hecto
Have you seen this? There is a discussion regarding the internal structure of APEX files and their relationship to the package manager which may be germane.Scar
@Scar Is it possible that APEX files are for system modules? I'm not sure I understand what this link says. And I already tested "isApex" and it was always false (as I wrote) ...Chalybite
APEX files are part of the Mainline project, but com.google.android.ext.services which is your example of an app that fails to reach the app-info screen doesn't have the right structure for an APEX file and looks like an ordinary APK. I was thinking that the apps that failed to reach the app-info screen are represented by APEX files and not standard APKS but, sadly, that does not seem to be the case. Also, filtering for MATCH_APEX has no effect when run on the emulator for API 29.Scar
@Scar So it's impossible to check which app is "APEX" ?Chalybite
That's a good question, but I don't have an answer to it. Here is some more info on the com.google.android.ext.services module that states "In Android 11, the ExtServices module (com.android.ext.services) is in APEX format. In Android 10, this module is in APK format.". So, it looks like it is all evolving. I haven't tried Android 11. Maybe looking at the code for a launcher app can provide some insight into how to sift through the apps.Scar
@Scar Do you think it's even related to the matter though?Chalybite
That is unclear to me. APEX files do appear in /system/apex on the emulator for API 30, but MATCH_APEX identifies the APEX files as well as non-APEX files. Unfortunately, the failure to display the app-info screen seems to be silent - at least nothing appears in logcat that I can discern. My opinion is that the the matters are related by that is just my unfounded opinion.Scar
Maybe the key word is "module" and not "mainline" in the response you received. See PackageManager#getInstalledModules(int) which is new with API 9.Scar
@Scar Almost. It has some apps that do not exist in getInstalledPackages, but this doesn't bother me much. Thing is, it also has "com.google.android.documentsui", which we can reach its app-info just fine (it's the "Files" app). So I tried to check isHidden, and this app is the only one (for me) that returns false for it. I don't know which out of all apps fail to reach app-info, but out of the list I got from getInstalledModules, seems those that do exist as installed apps (and hidden) - don't get to app-info. This includes the apps I've mentioned.Chalybite
@Scar So do you think that it's indeed the correct one? Meaning to get those that are hidden, out of the installed modules? There are so many system apps that I have no idea if it's indeed the correct answer or not.Chalybite
That is exactly the path I was following. I haven't checked the "Files" app but, on the stock emulator for API 30, app-info works for installed packages they either don't have an entry in modules or have an entry in modules that has isHidden as false. I think this is the right track.Scar
@Scar Seems as such. Please write it as an answer so that I could grant the bounty.Chalybite
Thanks, but I am also not convinced that this is the right answer. There are still 3 days left on the bounty. I will try to look at it in a little more detail. Meanwhile, maybe someone will have a better answer.Scar
S
3

The call to startActivity() is failing. Ideally, we would trace that call down into the Android system and figure out why it is failing to get some information that may lead to a fix. In lieu of that, I propose the following as a possible solution.

I am working off of the assumption that all installed packages can show an "apps-info" screen unless the package is a module that is hidden.

I have looked at an Android emulator running API 30 and the foregoing checks out. I am not convinced that this theory is valid in all cases. You mentioned the "Files" app as an issue. This app appears as a module but not in the list of installed apps as you suggested. The updated code addresses this.

I have put together a small app the can test whether apps-info screens are created or not depending upon the categories mentioned above. I have included it here for a further look. The comments in the code explain how the app works.

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)
    }
}
Scar answered 4/10, 2020 at 2:11 Comment(19)
"Files" app (and not "Files by Google" which is a different app) was just marked as a module, but is not hidden (and was the only one as such, out of all that I had). About the sample, you forgot to add implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'' to gradle. The part of the non-hidden modules shouldn't be commented, as it's supposed to still be safe (like "Files" app). I liked the way you tested it in the end (though you don't need coroutines for such a thing in POC, as you can use Handler instead very easily). The associate is a bit dangerous: Nullable fieldChalybite
Weird thing is that I don't see "Files" app here in your POC. I will check it out again later. Is it possible for associate to handle only non-nullable package names? I know it's not related to the topic. Just wondering...Chalybite
@androiddeveloper See updated app regarding "Files".Scar
Oh seems fine now. No idea how I missed it before. Also has "com.google.android.permissioncontroller" as a module that isn't hidden and indeed can be opened. You didn't answer me about associate . Do you know if there is any reason for packageName of ModuleInfo to be nullable (that's in the docs) ? Shouldn't associate skip such weird cases?Chalybite
@androiddeveloper ModuleInfo#getPackageName() can return a null, but I am not sure why that would be or even if it is something that the app would see IRL. As for "associate," a filter can be added to weed out null package name in case a null is present.Scar
@androiddeveloper Updated the code. Seems that the apps-info activity will start for hidden modules but the screen will not display. This will require separate testing since we cannot rely upon the activity count for hidden modules. Also now filter for null package names and do a straight post to check the activity count.Scar
I suggest to always prefer to use asSequence before filter, when possible. The reason is that without it, filter creates a new list on the way. Other than this, seems correct. Sadly I can't test it on various devices. Since it seems right here on emulator and Pixel device, I assume it's correct for all.Chalybite
I think that the failure is at this line which returns here which exits for a failure.Scar
An alternative method might be to detect that the mAppEntry entry will be missing before activity creation. I, however, do not know how to go about doing this.Scar
What failure are you talking about?Chalybite
@androiddeveloper The failure to show the apps-info screen.Scar
So I don't understand what you've found. Is it as we've found? Also, about the APEX checking, I've found that on API 30 it works fine, while on API 29 it's not. I probably tested well only on API 29, where I thought it doesn't work at all.Chalybite
It was just where the failure to show the apps-info screen occurs - just of interest and probably not germane. The module com.android.ext.services has migrated from a straight APK format to APEX in going from Android 10 to 11 according to Martin Zeitler's reference, so it makes sense to have one API work and not another if that is the module in question.Scar
Could APEX checking even reliable, assuming it worked ?Chalybite
So, the idea is that if a package is in the APEX format, then it will not show the app-info screen. We would just need to identify one package that is in the APEX format but shows the app-info screen to disprove this statement. On an emulator running API 30, the package com.google.android.cellbroadcast is in the APEX format but the app-info screen shows for it, so being a package in APEX format does not preclude the display of the app-info screen.Scar
So it's not reliable, even if it worked well. So APEX doesn't mean anything in this matter.Chalybite
I've published a new version of my app, and noticed that it got an exception for some user on getInstalledModules : "SecurityException: get installed packages: Neither user 15010364 nor current process has android.permission.INTERACT_ACROSS_USER" . How come? What is this?Chalybite
I see that you have already reported this. Nothing jumps out at me as the source of the problem or a solution. This is a mysterious permission (little/no documentation) and is annoying at best. Maybe something that is half-baked and not intended for release? Unsettling.Scar
You know me very well :) . I wonder if you use my app :)Chalybite
L
0

What they mean by "mainline module" might be explained here: Modular System Components.
In case these should be the only ones with this (non common app alike) behavior, one could identify them by their package-name. These would be all the package-names to filter for:

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

Currently I don't have the time to work on proof of concept, but the information seems reliable.
isApex should only be true for some of them (as the documentation suggests), therefore it cannot be used to identify them, but the package-name is a reliable criteria, that can be used.
String-comparison might not have been the expected answer, but is there any better criteria?

Lullaby answered 4/10, 2020 at 23:49 Comment(4)
How did you get this list exactly? using getInstalledModules as found on the other answer? If so, notice that your list includes some apps that you can actually reach their app-info screen (as shown there), and that like I wrote there, you should probably filter-out by checking if the module is hidden. If your answer is different, please explain why. Otherwise, it's like repeating another person's answer.Chalybite
I am not repeating anybody's answer, how you come to this conclusion?? This makes me feel sorry, that I've even answered. I've barely argumented with the documentation, which lists all of the package names of these "modular system components". And even if some may have that app-page, just remove them from the list, until there's only the ones left which don't. The ones which have the app-info page still might behave different than common APK would behave.Lullaby
I've asked about checking via code. You presented a list, so I asked where did you get it from, so that I could get it too (via code). Other devices might have more than the list in documentation, or less. Sorry you feel upset about this. I didn't mean to upset you. Docs are important, but I asked about code.Chalybite
As for isApex, I've noticed it's set to false for all apps I've found on Android API 30, but it is set to true for some apps on API 29, so while it might be a reliable way for API 29 (and I'm not sure about it), I don't think it is for API 30.Chalybite

© 2022 - 2024 — McMap. All rights reserved.