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

Unverified Commit 95d5fc19 authored by DaVinci9196's avatar DaVinci9196 Committed by GitHub
Browse files

Vending: Added PI access management (#3108)



Co-authored-by: default avatarMarvin W <git@larma.de>
parent 2aae6aff
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -279,6 +279,7 @@ object SettingsContract {
        const val ASSET_DEVICE_SYNC = "vending_device_sync"
        const val APPS_INSTALL = "vending_apps_install"
        const val APPS_INSTALLER_LIST = "vending_apps_installer_list"
        const val PLAY_INTEGRITY_APP_LIST = "vending_play_integrity_apps"

        val PROJECTION = arrayOf(
            LICENSING,
@@ -289,6 +290,7 @@ object SettingsContract {
            ASSET_DEVICE_SYNC,
            APPS_INSTALL,
            APPS_INSTALLER_LIST,
            PLAY_INTEGRITY_APP_LIST
        )
    }

+2 −0
Original line number Diff line number Diff line
@@ -369,6 +369,7 @@ class SettingsProvider : ContentProvider() {
            Vending.SPLIT_INSTALL -> getSettingsBoolean(key, false)
            Vending.APPS_INSTALL -> getSettingsBoolean(key, false)
            Vending.APPS_INSTALLER_LIST -> getSettingsString(key, "")
            Vending.PLAY_INTEGRITY_APP_LIST -> getSettingsString(key, "")
            else -> throw IllegalArgumentException("Unknown key: $key")
        }
    }
@@ -386,6 +387,7 @@ class SettingsProvider : ContentProvider() {
                Vending.ASSET_DEVICE_SYNC -> editor.putBoolean(key, value as Boolean)
                Vending.APPS_INSTALL -> editor.putBoolean(key, value as Boolean)
                Vending.APPS_INSTALLER_LIST -> editor.putString(key, value as String)
                Vending.PLAY_INTEGRITY_APP_LIST -> editor.putString(key, value as String)
                else -> throw IllegalArgumentException("Unknown key: $key")
            }
        }
+71 −0
Original line number Diff line number Diff line
/**
 * SPDX-FileCopyrightText: 2025 microG Project Team
 * SPDX-License-Identifier: Apache-2.0
 */

package org.microg.gms.vending

import org.json.JSONException
import org.json.JSONObject

class PlayIntegrityData(var allowed: Boolean,
                        val packageName: String,
                        val pkgSignSha256: String,
                        var lastTime: Long,
                        var lastResult: String? = null,
                        var lastStatus: Boolean = false) {

    override fun toString(): String {
        return JSONObject()
            .put(ALLOWED, allowed)
            .put(PACKAGE_NAME, packageName)
            .put(SIGNATURE, pkgSignSha256)
            .put(LAST_VISIT_TIME, lastTime)
            .put(LAST_VISIT_RESULT, lastResult)
            .put(LAST_VISIT_STATUS, lastStatus)
            .toString()
    }

    companion object {
        private const val PACKAGE_NAME = "packageName"
        private const val ALLOWED = "allowed"
        private const val SIGNATURE = "signature"
        private const val LAST_VISIT_TIME = "lastVisitTime"
        private const val LAST_VISIT_RESULT = "lastVisitResult"
        private const val LAST_VISIT_STATUS = "lastVisitStatus"

        private fun parse(jsonString: String): PlayIntegrityData? {
            try {
                val json = JSONObject(jsonString)
                return PlayIntegrityData(
                    json.getBoolean(ALLOWED),
                    json.getString(PACKAGE_NAME),
                    json.getString(SIGNATURE),
                    json.getLong(LAST_VISIT_TIME),
                    json.getString(LAST_VISIT_RESULT),
                    json.getBoolean(LAST_VISIT_STATUS)
                )
            } catch (e: JSONException) {
                return null
            }
        }

        fun loadDataSet(content: String): Set<PlayIntegrityData> {
            return content.split("|").mapNotNull { parse(it) }.toSet()
        }

        fun updateDataSetString(channelList: Set<PlayIntegrityData>, channel: PlayIntegrityData): String {
            val channelData = channelList.find { it.packageName == channel.packageName && it.pkgSignSha256 == channel.pkgSignSha256 }
            val newChannelList = if (channelData != null) {
                channelData.allowed = channel.allowed
                channelData.lastTime = channel.lastTime
                channelData.lastResult = channel.lastResult
                channelData.lastStatus = channel.lastStatus
                channelList
            } else {
                channelList + channel
            }
            return newChannelList.let { it -> it.joinToString(separator = "|") { it.toString() } }
        }
    }
}
 No newline at end of file
+5 −1
Original line number Diff line number Diff line
@@ -18,6 +18,8 @@ import com.google.android.gms.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.microg.gms.safetynet.SafetyNetDatabase
import org.microg.gms.vending.PlayIntegrityData
import org.microg.gms.vending.VendingPreferences

class SafetyNetAllAppsFragment : PreferenceFragmentCompat() {
    private lateinit var database: SafetyNetDatabase
@@ -50,8 +52,10 @@ class SafetyNetAllAppsFragment : PreferenceFragmentCompat() {
    private fun updateContent() {
        val context = requireContext()
        lifecycleScope.launchWhenResumed {
            val playIntegrityData = VendingPreferences.getPlayIntegrityAppList(context)
            val apps = withContext(Dispatchers.IO) {
                val res = database.recentApps.map { app ->
                val playPairs = PlayIntegrityData.loadDataSet(playIntegrityData).map { it.packageName to it.lastTime }
                val res = (database.recentApps + playPairs).map { app ->
                    val pref = AppIconPreference(context)
                    pref.packageName = app.first
                    pref.summary = when {
+44 −3
Original line number Diff line number Diff line
@@ -8,16 +8,26 @@ package org.microg.gms.ui
import android.annotation.SuppressLint
import android.os.Bundle
import android.text.format.DateUtils
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.preference.*
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import androidx.preference.isEmpty
import com.google.android.gms.R
import org.microg.gms.safetynet.SafetyNetDatabase
import org.microg.gms.safetynet.SafetyNetRequestType.*
import org.microg.gms.safetynet.SafetyNetRequestType.ATTESTATION
import org.microg.gms.safetynet.SafetyNetRequestType.RECAPTCHA
import org.microg.gms.safetynet.SafetyNetRequestType.RECAPTCHA_ENTERPRISE
import org.microg.gms.vending.PlayIntegrityData
import org.microg.gms.vending.VendingPreferences

class SafetyNetAppFragment : PreferenceFragmentCompat() {
    private lateinit var appHeadingPreference: AppHeadingPreference
    private lateinit var recents: PreferenceCategory
    private lateinit var recentsNone: Preference
    private lateinit var allowRequests: SwitchPreferenceCompat
    private val packageName: String?
        get() = arguments?.getString("package")

@@ -30,6 +40,16 @@ class SafetyNetAppFragment : PreferenceFragmentCompat() {
        appHeadingPreference = preferenceScreen.findPreference("pref_safetynet_app_heading") ?: appHeadingPreference
        recents = preferenceScreen.findPreference("prefcat_safetynet_recent_list") ?: recents
        recentsNone = preferenceScreen.findPreference("pref_safetynet_recent_none") ?: recentsNone
        allowRequests = preferenceScreen.findPreference("pref_device_attestation_app_allow_requests") ?: allowRequests
        allowRequests.setOnPreferenceChangeListener { _, newValue ->
            val playIntegrityDataSet = loadPlayIntegrityData()
            val integrityData = packageName?.let { packageName -> playIntegrityDataSet.find { packageName == it.packageName } }
            if (newValue is Boolean && integrityData != null) {
                val content = PlayIntegrityData.updateDataSetString(playIntegrityDataSet, integrityData.apply { this.allowed = newValue })
                VendingPreferences.setPlayIntegrityAppList(requireContext(), content)
            }
            true
        }
    }

    override fun onResume() {
@@ -37,6 +57,11 @@ class SafetyNetAppFragment : PreferenceFragmentCompat() {
        updateContent()
    }

    private fun loadPlayIntegrityData(): Set<PlayIntegrityData> {
        val playIntegrityData = VendingPreferences.getPlayIntegrityAppList(requireContext())
        return PlayIntegrityData.loadDataSet(playIntegrityData)
    }

    fun updateContent() {
        lifecycleScope.launchWhenResumed {
            appHeadingPreference.packageName = packageName
@@ -52,7 +77,6 @@ class SafetyNetAppFragment : PreferenceFragmentCompat() {
                }.orEmpty()
            recents.removeAll()
            recents.addPreference(recentsNone)
            recentsNone.isVisible = summaries.isEmpty()
            for (summary in summaries) {
                val preference = Preference(requireContext())
                preference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
@@ -84,6 +108,23 @@ class SafetyNetAppFragment : PreferenceFragmentCompat() {
                }
                recents.addPreference(preference)
            }
            val piContent = packageName?.let { packageName -> loadPlayIntegrityData().find { packageName == it.packageName } }
            if (piContent != null) {
                val preference = Preference(requireContext())
                val date = DateUtils.getRelativeDateTimeString(
                    context,
                    piContent.lastTime,
                    DateUtils.MINUTE_IN_MILLIS,
                    DateUtils.WEEK_IN_MILLIS,
                    DateUtils.FORMAT_SHOW_TIME
                )
                preference.title = date
                preference.summary = piContent.lastResult
                preference.icon = if (piContent.lastStatus) ContextCompat.getDrawable(context, R.drawable.ic_circle_check) else ContextCompat.getDrawable(context, R.drawable.ic_circle_warn)
                recents.addPreference(preference)
            }
            recentsNone.isVisible = summaries.isEmpty() && piContent == null
            allowRequests.isChecked = piContent?.allowed == true
        }

    }
Loading