Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?
like image 868
android developer Avatar asked Sep 25 '20 11:09

android developer


1 Answers

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)
    }
}
like image 57
Cheticamp Avatar answered Oct 24 '22 16:10

Cheticamp