diff --git a/play-services-base/core/src/main/AndroidManifest.xml b/play-services-base/core/src/main/AndroidManifest.xml index aebc21a461..5f539647cc 100644 --- a/play-services-base/core/src/main/AndroidManifest.xml +++ b/play-services-base/core/src/main/AndroidManifest.xml @@ -5,10 +5,17 @@ --> + + + + android:exported="true" + android:readPermission="${applicationId}.permission.READ_SETTINGS" + android:writePermission="${applicationId}.permission.WRITE_SETTINGS" /> diff --git a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt index 2d59c173b2..56aa4bd61f 100644 --- a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt +++ b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsContract.kt @@ -171,6 +171,18 @@ object SettingsContract { ) } + object Vending { + private const val id = "vending" + fun getContentUri(context: Context) = Uri.withAppendedPath(getAuthorityUri(context), id) + fun getContentType(context: Context) = "vnd.android.cursor.item/vnd.${getAuthority(context)}.$id" + + const val LICENSING = "vending_licensing" + + val PROJECTION = arrayOf( + LICENSING + ) + } + private fun withoutCallingIdentity(f: () -> T): T { val identity = Binder.clearCallingIdentity() try { diff --git a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt index 01d51a31a0..8bcfd1ecf5 100644 --- a/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt +++ b/play-services-base/core/src/main/kotlin/org/microg/gms/settings/SettingsProvider.kt @@ -22,6 +22,7 @@ import org.microg.gms.settings.SettingsContract.DroidGuard import org.microg.gms.settings.SettingsContract.Exposure import org.microg.gms.settings.SettingsContract.Gcm import org.microg.gms.settings.SettingsContract.Location +import org.microg.gms.settings.SettingsContract.Vending import org.microg.gms.settings.SettingsContract.Profile import org.microg.gms.settings.SettingsContract.SafetyNet import org.microg.gms.settings.SettingsContract.getAuthority @@ -78,6 +79,7 @@ class SettingsProvider : ContentProvider() { DroidGuard.getContentUri(context!!) -> queryDroidGuard(projection ?: DroidGuard.PROJECTION) Profile.getContentUri(context!!) -> queryProfile(projection ?: Profile.PROJECTION) Location.getContentUri(context!!) -> queryLocation(projection ?: Location.PROJECTION) + Vending.getContentUri(context!!) -> queryVending(projection ?: Vending.PROJECTION) else -> null } @@ -98,6 +100,7 @@ class SettingsProvider : ContentProvider() { DroidGuard.getContentUri(context!!) -> updateDroidGuard(values) Profile.getContentUri(context!!) -> updateProfile(values) Location.getContentUri(context!!) -> updateLocation(values) + Vending.getContentUri(context!!) -> updateVending(values) else -> return 0 } return 1 @@ -335,6 +338,25 @@ class SettingsProvider : ContentProvider() { editor.apply() } + private fun queryVending(p: Array): Cursor = MatrixCursor(p).addRow(p) { key -> + when (key) { + Vending.LICENSING -> getSettingsBoolean(key, false) + else -> throw IllegalArgumentException("Unknown key: $key") + } + } + + private fun updateVending(values: ContentValues) { + if (values.size() == 0) return + val editor = preferences.edit() + values.valueSet().forEach { (key, value) -> + when (key) { + Vending.LICENSING -> editor.putBoolean(key, value as Boolean) + else -> throw IllegalArgumentException("Unknown key: $key") + } + } + editor.apply() + } + private fun MatrixCursor.addRow( p: Array, valueGetter: (String) -> Any? diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt index 6d3d6f7d23..703f80914a 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/SettingsFragment.kt @@ -15,6 +15,7 @@ import com.google.android.gms.R import org.microg.gms.checkin.CheckinPreferences import org.microg.gms.gcm.GcmDatabase import org.microg.gms.gcm.GcmPrefs +import org.microg.gms.vending.VendingPreferences import org.microg.gms.safetynet.SafetyNetPreferences import org.microg.gms.ui.settings.SettingsProvider import org.microg.gms.ui.settings.getAllSettingsProviders @@ -42,11 +43,18 @@ class SettingsFragment : ResourceSettingsFragment() { findNavController().navigate(requireContext(), R.id.openLocationSettings) true } - findPreference(PREF_ABOUT)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { - findNavController().navigate(requireContext(), R.id.openAbout) + findPreference(PREF_VENDING)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { + findNavController().navigate(requireContext(), R.id.openVendingSettings) true } - findPreference(PREF_ABOUT)!!.summary = getString(org.microg.tools.ui.R.string.about_version_str, AboutFragment.getSelfVersion(context)) + + findPreference(PREF_ABOUT)!!.apply { + onPreferenceClickListener = Preference.OnPreferenceClickListener { + findNavController().navigate(requireContext(), R.id.openAbout) + true + } + summary = getString(org.microg.tools.ui.R.string.about_version_str, AboutFragment.getSelfVersion(context)) + } for (entry in getAllSettingsProviders(requireContext()).flatMap { it.getEntriesStatic(requireContext()) }) { entry.createPreference() @@ -101,6 +109,7 @@ class SettingsFragment : ResourceSettingsFragment() { findPreference(PREF_CHECKIN)!!.setSummary(if (CheckinPreferences.isEnabled(requireContext())) org.microg.gms.base.core.R.string.service_status_enabled_short else org.microg.gms.base.core.R.string.service_status_disabled_short) findPreference(PREF_SNET)!!.setSummary(if (SafetyNetPreferences.isEnabled(requireContext())) org.microg.gms.base.core.R.string.service_status_enabled_short else org.microg.gms.base.core.R.string.service_status_disabled_short) + findPreference(PREF_VENDING)!!.setSummary(if (VendingPreferences.isLicensingEnabled(requireContext())) R.string.pref_vending_summary_licensing_on else R.string.pref_vending_summary_licensing_off) lifecycleScope.launchWhenResumed { val entries = getAllSettingsProviders(requireContext()).flatMap { it.getEntriesDynamic(requireContext()) } @@ -121,6 +130,7 @@ class SettingsFragment : ResourceSettingsFragment() { const val PREF_SNET = "pref_snet" const val PREF_LOCATION = "pref_location" const val PREF_CHECKIN = "pref_checkin" + const val PREF_VENDING = "pref_vending" } init { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt b/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt new file mode 100644 index 0000000000..72750dcc4b --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/ui/VendingFragment.kt @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: 2023, e Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.ui + +import android.annotation.SuppressLint +import android.os.Bundle +import androidx.lifecycle.lifecycleScope +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.TwoStatePreference +import com.google.android.gms.R +import org.microg.gms.vending.VendingPreferences + +class VendingFragment : PreferenceFragmentCompat() { + private lateinit var licensingEnabled: TwoStatePreference + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + addPreferencesFromResource(R.xml.preferences_vending) + } + + @SuppressLint("RestrictedApi") + override fun onBindPreferences() { + licensingEnabled = preferenceScreen.findPreference(PREF_LICENSING_ENABLED) ?: licensingEnabled + licensingEnabled.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue -> + val appContext = requireContext().applicationContext + lifecycleScope.launchWhenResumed { + if (newValue is Boolean) { + VendingPreferences.setLicensingEnabled(appContext, newValue) + } + updateContent() + } + true + } + } + + override fun onResume() { + super.onResume() + updateContent() + } + + private fun updateContent() { + val appContext = requireContext().applicationContext + lifecycleScope.launchWhenResumed { + licensingEnabled.isChecked = VendingPreferences.isLicensingEnabled(appContext) + } + } + + companion object { + const val PREF_LICENSING_ENABLED = "vending_licensing" + } +} \ No newline at end of file diff --git a/play-services-core/src/main/kotlin/org/microg/gms/vending/VendingPreferences.kt b/play-services-core/src/main/kotlin/org/microg/gms/vending/VendingPreferences.kt new file mode 100644 index 0000000000..a3d631d3c1 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/vending/VendingPreferences.kt @@ -0,0 +1,26 @@ +/* + * SPDX-FileCopyrightText: 2023, e Foundation + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.vending + +import android.content.Context +import org.microg.gms.settings.SettingsContract + +object VendingPreferences { + @JvmStatic + fun isLicensingEnabled(context: Context): Boolean { + val projection = arrayOf(SettingsContract.Vending.LICENSING) + return SettingsContract.getSettings(context, SettingsContract.Vending.getContentUri(context), projection) { c -> + c.getInt(0) != 0 + } + } + + @JvmStatic + fun setLicensingEnabled(context: Context, enabled: Boolean) { + SettingsContract.setSettings(context, SettingsContract.Vending.getContentUri(context)) { + put(SettingsContract.Vending.LICENSING, enabled) + } + } +} \ No newline at end of file diff --git a/play-services-core/src/main/res/drawable/ic_shop.xml b/play-services-core/src/main/res/drawable/ic_shop.xml new file mode 100644 index 0000000000..cf3c794285 --- /dev/null +++ b/play-services-core/src/main/res/drawable/ic_shop.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/play-services-core/src/main/res/navigation/nav_settings.xml b/play-services-core/src/main/res/navigation/nav_settings.xml index 7effe7c323..d9a7fc3938 100644 --- a/play-services-core/src/main/res/navigation/nav_settings.xml +++ b/play-services-core/src/main/res/navigation/nav_settings.xml @@ -23,6 +23,9 @@ + @@ -134,6 +137,13 @@ app:argType="string" /> + + + + Google device registration Cloud Messaging Google SafetyNet + Play Store services Google Play Games %1$s would like to use Play Games @@ -231,4 +232,9 @@ This can take a couple of minutes." ON / Manual: %s %s seconds %s minutes + + Licensing off + Licensing on + Answer license verification requests + Some apps require verification that you have purchased them on Google Play. When requested by an app, microG can download a proof of purchase from Google. If disabled, or if no Google account is added, requests for license verification are ignored. diff --git a/play-services-core/src/main/res/xml/preferences_start.xml b/play-services-core/src/main/res/xml/preferences_start.xml index 2336cf9aac..c2b618367f 100644 --- a/play-services-core/src/main/res/xml/preferences_start.xml +++ b/play-services-core/src/main/res/xml/preferences_start.xml @@ -47,6 +47,10 @@ android:icon="@drawable/ic_certificate" android:key="pref_snet" android:title="@string/service_name_snet" /> + + + + + + + + + + +