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?
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • 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`. – aminography Sep 30 '20 at 08:19
  • Have you seen [this](https://source.android.com/devices/tech/ota/apex#apex_files_are_apk_files)? There is a discussion regarding the internal structure of _APEX_ files and their relationship to the package manager which may be germane. – Cheticamp Oct 01 '20 at 13:45
  • @Cheticamp 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) ... – android developer Oct 02 '20 at 15:22
  • 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](https://developer.android.com/reference/android/content/pm/PackageManager#MATCH_APEX) has no effect when run on the emulator for API 29. – Cheticamp Oct 02 '20 at 17:39
  • @Cheticamp So it's impossible to check which app is "APEX" ? – android developer Oct 03 '20 at 11:18
  • That's a good question, but I don't have an answer to it. [Here](https://source.android.com/devices/architecture/modular-system/extservices) 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. – Cheticamp Oct 03 '20 at 12:31
  • @Cheticamp Do you think it's even related to the matter though? – android developer Oct 03 '20 at 13:13
  • 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. – Cheticamp Oct 03 '20 at 14:28
  • 2
    Maybe the key word is "module" and not "mainline" in the response you received. See [PackageManager#getInstalledModules(int)](https://developer.android.com/reference/android/content/pm/PackageManager#getInstalledModules(int)) which is new with API 9. – Cheticamp Oct 03 '20 at 15:25
  • @Cheticamp 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. – android developer Oct 03 '20 at 21:43
  • @Cheticamp 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. – android developer Oct 03 '20 at 21:44
  • 1
    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. – Cheticamp Oct 03 '20 at 22:21
  • @Cheticamp Seems as such. Please write it as an answer so that I could grant the bounty. – android developer Oct 03 '20 at 23:50
  • 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. – Cheticamp Oct 04 '20 at 00:42

2 Answers2

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)
    }
}
Cheticamp
  • 61,413
  • 10
  • 78
  • 131
  • "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 field – android developer Oct 04 '20 at 06:38
  • 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... – android developer Oct 04 '20 at 06:39
  • 1
    @androiddeveloper See updated app regarding "Files". – Cheticamp Oct 04 '20 at 11:31
  • 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? – android developer Oct 04 '20 at 18:41
  • @androiddeveloper [_ModuleInfo#getPackageName()_](https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/content/pm/ModuleInfo.java;l=84;drc=master?q=moduleinfo&ss=android%2Fplatform%2Fsuperproject) 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. – Cheticamp Oct 04 '20 at 19:26
  • @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. – Cheticamp Oct 04 '20 at 23:23
  • 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. – android developer Oct 05 '20 at 11:29
  • I think that the failure is at [this line](https://cs.android.com/android/platform/superproject/+/master:packages/apps/Settings/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java;l=589?q=Missing%20AppEntry;%20maybe%20reinstalling%3F&ss=android%2Fplatform%2Fsuperproject) which returns [here](https://cs.android.com/android/platform/superproject/+/master:packages/apps/Settings/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java;l=450?q=Missing%20AppEntry;%20maybe%20reinstalling%3F&ss=android%2Fplatform%2Fsuperproject) which exits for a failure. – Cheticamp Oct 05 '20 at 12:16
  • 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. – Cheticamp Oct 05 '20 at 14:32
  • What failure are you talking about? – android developer Oct 05 '20 at 17:52
  • @androiddeveloper The failure to show the apps-info screen. – Cheticamp Oct 05 '20 at 21:48
  • 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. – android developer Oct 05 '20 at 22:14
  • 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](https://source.android.com/devices/architecture/modular-system), so it makes sense to have one API work and not another if that is the module in question. – Cheticamp Oct 05 '20 at 22:40
  • Could APEX checking even reliable, assuming it worked ? – android developer Oct 06 '20 at 07:33
  • 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. – Cheticamp Oct 06 '20 at 11:50
  • So it's not reliable, even if it worked well. So APEX doesn't mean anything in this matter. – android developer Oct 06 '20 at 13:41
  • 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? – android developer Oct 10 '20 at 20:38
  • I see that you have already [reported this](https://issuetracker.google.com/issues/170471469). 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. – Cheticamp Oct 10 '20 at 22:20
  • You know me very well :) . I wonder if you use my app :) – android developer Oct 10 '20 at 22:35
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?

Martin Zeitler
  • 1
  • 19
  • 155
  • 216
  • 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. – android developer Oct 05 '20 at 11:28
  • 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. – Martin Zeitler Oct 06 '20 at 03:37
  • 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. – android developer Oct 06 '20 at 07:34
  • 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. – android developer Oct 06 '20 at 07:37