Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8672f2eb authored by Kurt Melby's avatar Kurt Melby
Browse files

Extract `ServiceListing` code to `AppsNotificationAccessScreen`

An unrelated part of `ServiceListing` was creating an
`android.os.Handler`, which was breaking the `PreferenceServiceTest`
tests since the setup code isn't run on the main Android thread.

Bug: 415243959, 415926645
Test: adb shell cmd app_function execute-app-function --package com.android.settings --function getPermissionsDeviceState --parameters {} | grep notification_access -C 20
Flag: com.android.settings.flags.device_state
Change-Id: I4c5b6e6234baff5c3f7fad2c5454d327ffbf86e9
parent c2e177e7
Loading
Loading
Loading
Loading
+90 −11
Original line number Diff line number Diff line
@@ -17,17 +17,20 @@
package com.android.settings.spa.app.catalyst

import android.Manifest
import android.app.ActivityManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ServiceInfo
import android.os.Bundle
import android.provider.Settings
import android.provider.Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS
import android.service.notification.NotificationListenerService
import android.util.Log
import com.android.settings.R
import com.android.settings.contract.TAG_DEVICE_STATE_SCREEN
import com.android.settings.flags.Flags
import com.android.settingslib.applications.ServiceListing
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.ProvidePreferenceScreen
import com.android.settingslib.metadata.preferenceHierarchy
@@ -73,18 +76,94 @@ class AppsNotificationAccessScreen : PreferenceScreenCreator {
    companion object {
        const val KEY = "device_state_apps_notification_access"

        // TODO: b/416239475 - unify this code with ServiceListing.java
        @JvmStatic
        fun loadNotificationListenerServices(context: Context): List<ServiceInfo> {
            val serviceListing =
                ServiceListing.Builder(context)
                    .setIntentAction(NotificationListenerService.SERVICE_INTERFACE)
                    .setPermission(Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE)
                    .setNoun("notification listener")
                    .setSetting(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS)
                    .setTag(TAG_DEVICE_STATE_SCREEN)
                    .build()
            val services = serviceListing.load()
        private fun loadEnabledServices(context: Context, setting: String): List<ComponentName> {
            val enabledServices = mutableListOf<ComponentName>()
            enabledServices.clear()

            val contentResolver = context.getContentResolver()
            val flat = Settings.Secure.getString(contentResolver, setting)
            if (!flat.isNullOrEmpty()) {
                val names = flat.split(":")
                for (name in names) {
                    ComponentName.unflattenFromString(name)?.let { enabledServices.add(it) }
                }
            }

            return enabledServices
        }

        /**
         * Loads services matching the given intent and permission, filtered to the apps that
         * request the service via a setting.
         *
         * This is based on com.android.settingslib.applications.ServiceListing#load().
         *
         * @param context The context to use.
         * @param intentAction The intent action to match.
         * @param permission The permission to require.
         * @param setting The setting to use to store enabled services.
         * @return A list of services that match the given intent and permission.
         */
        // TODO: b/416239475 - unify this code with ServiceListing.java
        @JvmStatic
        private fun loadServices(
            context: Context,
            intentAction: String,
            permission: String,
            setting: String,
        ): List<ServiceInfo> {
            val enabledServices = loadEnabledServices(context, setting)
            val services = mutableListOf<ServiceInfo>()
            val user = ActivityManager.getCurrentUser()

            var flags = PackageManager.GET_SERVICES or PackageManager.GET_META_DATA

            // Add requesting apps, with full validation
            val installedServices =
                context.packageManager.queryIntentServicesAsUser(Intent(intentAction), flags, user)
            for (resolveInfo in installedServices) {
                val info = resolveInfo.serviceInfo

                if (info.componentName !in enabledServices) {
                    if (permission != info.permission) {
                        Log.w(
                            TAG_DEVICE_STATE_SCREEN,
                            "Skipping service ${info.packageName}/${info.name}: " +
                                "it does not require the permission $permission",
                        )
                        continue
                    }
                    services.add(info)
                }
            }

            // Add all apps with access, in case prior approval was granted without full validation
            for (componentName in enabledServices) {
                val enabledServicesResolveInfo =
                    context.packageManager.queryIntentServicesAsUser(
                        Intent().setComponent(componentName),
                        flags,
                        user,
                    )
                for (resolveInfo in enabledServicesResolveInfo) {
                    val info = resolveInfo.serviceInfo
                    services.add(info)
                }
            }

            return services
        }

        @JvmStatic
        fun loadNotificationListenerServices(context: Context): List<ServiceInfo> {
            return loadServices(
                context,
                NotificationListenerService.SERVICE_INTERFACE,
                Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE,
                Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
            )
        }
    }
}