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

Commit 60072553 authored by Fan Wu's avatar Fan Wu Committed by Android (Google) Code Review
Browse files

Merge "Migrate Display over other apps info page for Device State" into main

parents d79ffbf5 0285b12f
Loading
Loading
Loading
Loading
+140 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.settings.spa.app.catalyst

import android.Manifest.permission.SYSTEM_ALERT_WINDOW
import android.app.Application
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Bundle
import com.android.settings.R
import com.android.settings.applications.AppStateOverlayBridge
import com.android.settings.contract.TAG_DEVICE_STATE_PREFERENCE
import com.android.settings.contract.TAG_DEVICE_STATE_SCREEN
import com.android.settings.flags.Flags
import com.android.settingslib.applications.ApplicationsState
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.NoOpKeyedObservable
import com.android.settingslib.metadata.MainSwitchPreference
import com.android.settingslib.metadata.PreferenceSummaryProvider
import com.android.settingslib.metadata.PreferenceTitleProvider
import com.android.settingslib.metadata.ProvidePreferenceScreen
import com.android.settingslib.metadata.preferenceHierarchy
import com.android.settingslib.preference.PreferenceFragment
import com.android.settingslib.preference.PreferenceScreenCreator
import com.android.settingslib.spaprivileged.model.app.AppListRepositoryImpl
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow

// Note: This page is for DeviceState usages.
@ProvidePreferenceScreen(AppInfoDisplayOverOtherAppsScreen.KEY, parameterized = true)
class AppInfoDisplayOverOtherAppsScreen(context: Context, override val arguments: Bundle) :
    PreferenceScreenCreator, PreferenceSummaryProvider, PreferenceTitleProvider {

    private val packageName = arguments.getString("app")!!

    private val appInfo = context.packageManager.getApplicationInfo(packageName, 0)

    private val storage: KeyValueStore = DisplayOverOtherAppsStorage(context, appInfo, packageName)

    override val key: String
        get() = KEY

    override val screenTitle: Int
        get() = R.string.draw_overlay

    override fun tags(context: Context) =
        arrayOf(TAG_DEVICE_STATE_SCREEN, TAG_DEVICE_STATE_PREFERENCE)

    override fun getTitle(context: Context): CharSequence =
        appInfo.loadLabel(context.packageManager)

    override fun getSummary(context: Context): CharSequence =
        context.getString(
            when (storage.getBoolean(DisplayOverOtherAppsMainSwitch.KEY)) {
                true -> R.string.app_permission_summary_allowed
                else -> R.string.app_permission_summary_not_allowed
            }
        )

    override fun isFlagEnabled(context: Context) = Flags.deviceState()

    override fun hasCompleteHierarchy() = false

    override fun fragmentClass() = PreferenceFragment::class.java

    override fun getPreferenceHierarchy(context: Context) =
        preferenceHierarchy(context, this) { +DisplayOverOtherAppsMainSwitch(storage) }

    companion object {
        const val KEY = "device_state_app_info_display_over_other_apps"

        @JvmStatic
        fun parameters(context: Context): Flow<Bundle> = flow {
            val repo = AppListRepositoryImpl(context)
            repo.loadAndFilterApps(context.userId, true).forEach { app ->
                if (app.hasOverlayPermission(context)) {
                    emit(Bundle(1).apply { putString("app", app.packageName) })
                }
            }
        }

        private fun ApplicationInfo.hasOverlayPermission(context: Context): Boolean =
            try {
                val packageInfo: PackageInfo =
                    context.packageManager.getPackageInfo(
                        this.packageName,
                        PackageManager.GET_PERMISSIONS,
                    )
                val requestedPermissions = packageInfo.requestedPermissions
                requestedPermissions?.contains(SYSTEM_ALERT_WINDOW) == true
            } catch (e: Exception) {
                false
            }
    }
}

private class DisplayOverOtherAppsMainSwitch(private val storage: KeyValueStore) :
    MainSwitchPreference(KEY, R.string.permit_draw_overlay) {

    override fun storage(context: Context) = storage

    companion object {
        const val KEY = "device_state_app_ops_settings_switch"
    }
}

private class DisplayOverOtherAppsStorage(
    context: Context,
    private val appInfo: ApplicationInfo,
    private val packageName: String,
) : NoOpKeyedObservable<String>(), KeyValueStore {

    val state: ApplicationsState =
        ApplicationsState.getInstance(context.applicationContext as Application)
    val overlayBridge = AppStateOverlayBridge(context, state, null)

    override fun contains(key: String): Boolean = true

    @Suppress("UNCHECKED_CAST")
    override fun <T : Any> getValue(key: String, valueType: Class<T>): T =
        (overlayBridge.getOverlayInfo(packageName, appInfo.uid).isPermissible) as T

    override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {}
}