Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ object SettingsContract {
const val BILLING = "vending_billing"
const val ASSET_DELIVERY = "vending_asset_delivery"
const val ASSET_DEVICE_SYNC = "vending_device_sync"
const val APPS_INSTALL = "vending_apps_install"
const val APPS_INSTALLER_LIST = "vending_apps_installer_list"

val PROJECTION = arrayOf(
LICENSING,
Expand All @@ -285,6 +287,8 @@ object SettingsContract {
BILLING,
ASSET_DELIVERY,
ASSET_DEVICE_SYNC,
APPS_INSTALL,
APPS_INSTALLER_LIST,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import android.content.ContentValues
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.SharedPreferences
import android.content.pm.ApplicationInfo
import android.database.Cursor
import android.database.MatrixCursor
import android.net.Uri
Expand Down Expand Up @@ -368,6 +367,8 @@ class SettingsProvider : ContentProvider() {
Vending.ASSET_DELIVERY -> getSettingsBoolean(key, false)
Vending.ASSET_DEVICE_SYNC -> getSettingsBoolean(key, false)
Vending.SPLIT_INSTALL -> getSettingsBoolean(key, false)
Vending.APPS_INSTALL -> getSettingsBoolean(key, false)
Vending.APPS_INSTALLER_LIST -> getSettingsString(key, "")
else -> throw IllegalArgumentException("Unknown key: $key")
}
}
Expand All @@ -383,6 +384,8 @@ class SettingsProvider : ContentProvider() {
Vending.SPLIT_INSTALL -> editor.putBoolean(key, value as Boolean)
Vending.ASSET_DELIVERY -> editor.putBoolean(key, value as Boolean)
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)
else -> throw IllegalArgumentException("Unknown key: $key")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* 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

enum class AllowType(val value: Int) {
REJECT_ALWAYS(0),
REJECT_ONCE(1),
ALLOW_ONCE(2),
ALLOW_ALWAYS(3),
}

data class InstallerData(val packageName: String, var allowType: Int, val pkgSignSha256: String) {

override fun toString(): String {
return JSONObject()
.put(CHANNEL_PACKAGE_NAME, packageName)
.put(CHANNEL_ALLOW_TYPE, allowType)
.put(CHANNEL_SIGNATURE, pkgSignSha256)
.toString()
}

companion object {
private const val CHANNEL_PACKAGE_NAME = "packageName"
private const val CHANNEL_ALLOW_TYPE = "allowType"
private const val CHANNEL_SIGNATURE = "signature"

private fun parse(jsonString: String): InstallerData? {
try {
val json = JSONObject(jsonString)
return InstallerData(
json.getString(CHANNEL_PACKAGE_NAME),
json.getInt(CHANNEL_ALLOW_TYPE),
json.getString(CHANNEL_SIGNATURE)
)
} catch (e: JSONException) {
return null
}
}

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

fun updateDataSetString(channelList: Set<InstallerData>, channel: InstallerData): String {
val channelData = channelList.find { it.packageName == channel.packageName && it.pkgSignSha256 == channel.pkgSignSha256 }
val newChannelList = if (channelData != null) {
channelData.allowType = channel.allowType
channelList
} else {
channelList + channel
}
return newChannelList.let { it -> it.joinToString(separator = "|") { it.toString() } }
}
}
}
3 changes: 3 additions & 0 deletions play-services-core/src/huawei/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,8 @@
<meta-data
android:name="org.microg.gms.settings.allow_upload_game_played"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.vending_apps_install"
android:value="true" />
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ package org.microg.gms.ui

import android.annotation.SuppressLint
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.TwoStatePreference
Expand Down Expand Up @@ -118,7 +122,27 @@ class VendingFragment : PreferenceFragmentCompat() {
}
}

init {
setHasOptionsMenu(true)
}

override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
menu.add(0, MENU_INSTALL_MANAGED, 0, R.string.pref_app_install_settings_title)
super.onCreateOptionsMenu(menu, inflater)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
MENU_INSTALL_MANAGED -> {
findNavController().navigate(requireContext(), R.id.openVendingInstallSettings)
true
}

else -> super.onOptionsItemSelected(item)
}
}
companion object {
private const val MENU_INSTALL_MANAGED = Menu.FIRST
const val PREF_LICENSING_ENABLED = "vending_licensing"
const val PREF_LICENSING_PURCHASE_FREE_APPS_ENABLED = "vending_licensing_purchase_free_apps"
const val PREF_SPLIT_INSTALL_ENABLED = "vending_split_install"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* SPDX-FileCopyrightText: 2025 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.ui

import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import com.google.android.gms.R
import org.microg.gms.utils.getApplicationLabel
import org.microg.gms.vending.AllowType
import org.microg.gms.vending.InstallerData
import org.microg.gms.vending.VendingPreferences

class VendingInstallSettingsFragment : PreferenceFragmentCompat() {
private lateinit var switchBarPreference: SwitchBarPreference
private lateinit var installers: PreferenceCategory
private lateinit var none: Preference

override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.preferences_vending_installer_settings)

switchBarPreference = preferenceScreen.findPreference("pref_vending_allow_install_apps") ?: switchBarPreference
installers = preferenceScreen.findPreference("pref_permission_installer_settings") ?: installers
none = preferenceScreen.findPreference("pref_permission_installer_none") ?: none

switchBarPreference.setOnPreferenceChangeListener { _, newValue ->
val appContext = requireContext().applicationContext
lifecycleScope.launchWhenResumed {
if (newValue is Boolean) {
VendingPreferences.setInstallEnabled(appContext, newValue)
}
updateContent()
}
true
}
}

override fun onResume() {
super.onResume()
updateContent()
}

fun updateContent() {
val appContext = requireContext().applicationContext
lifecycleScope.launchWhenResumed {
installers.isVisible = VendingPreferences.isInstallEnabled(appContext)
switchBarPreference.isChecked = VendingPreferences.isInstallEnabled(appContext)
val installerList = VendingPreferences.getInstallerList(appContext)
val installerDataSet = InstallerData.loadDataSet(installerList)
val installerViews = installerDataSet.mapNotNull {
runCatching {
SwitchPreference(appContext).apply {
key = "pref_permission_channels_${it.packageName}"
title = appContext.packageManager.getApplicationLabel(it.packageName)
icon = appContext.packageManager.getApplicationIcon(it.packageName)
isChecked = it.allowType == AllowType.ALLOW_ALWAYS.value
setOnPreferenceChangeListener { _, newValue ->
lifecycleScope.launchWhenResumed {
if (newValue is Boolean) {
val allowType = if (newValue) AllowType.ALLOW_ALWAYS.value else AllowType.REJECT_ALWAYS.value
val content = InstallerData.updateDataSetString(installerDataSet, it.apply { this.allowType = allowType })
VendingPreferences.setInstallerList(appContext, content)
}
}
true
}
}
}.getOrNull()
}
installers.removeAll()
for (installerView in installerViews) {
installers.addPreference(installerView)
}
if (installerViews.isEmpty()) {
installers.addPreference(none)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,34 @@ object VendingPreferences {
put(SettingsContract.Vending.ASSET_DEVICE_SYNC, enabled)
}
}

@JvmStatic
fun isInstallEnabled(context: Context): Boolean {
val projection = arrayOf(SettingsContract.Vending.APPS_INSTALL)
return SettingsContract.getSettings(context, SettingsContract.Vending.getContentUri(context), projection) { c ->
c.getInt(0) != 0
}
}

@JvmStatic
fun setInstallEnabled(context: Context, enabled: Boolean) {
SettingsContract.setSettings(context, SettingsContract.Vending.getContentUri(context)) {
put(SettingsContract.Vending.APPS_INSTALL, enabled)
}
}

@JvmStatic
fun getInstallerList(context: Context): String {
val projection = arrayOf(SettingsContract.Vending.APPS_INSTALLER_LIST)
return SettingsContract.getSettings(context, SettingsContract.Vending.getContentUri(context), projection) { c ->
c.getString(0)
}
}

@JvmStatic
fun setInstallerList(context: Context, content: String) {
SettingsContract.setSettings(context, SettingsContract.Vending.getContentUri(context)) {
put(SettingsContract.Vending.APPS_INSTALLER_LIST, content)
}
}
}
13 changes: 12 additions & 1 deletion play-services-core/src/main/res/navigation/nav_settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,18 @@
<fragment
android:id="@+id/vendingFragment"
android:name="org.microg.gms.ui.VendingFragment"
android:label="@string/service_name_vending" />
android:label="@string/service_name_vending">
<action
android:id="@+id/openVendingInstallSettings"
app:destination="@id/vendingInstallSettingsFragment"/>
</fragment>

<!-- Install Settings -->

<fragment
android:id="@+id/vendingInstallSettingsFragment"
android:name="org.microg.gms.ui.VendingInstallSettingsFragment"
android:label="@string/pref_app_install_settings_title" />

<!-- Work profile -->

Expand Down
6 changes: 6 additions & 0 deletions play-services-core/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -347,4 +347,10 @@ microG GmsCore 内置一套自由的 SafetyNet 实现,但是官方服务器要
<string name="pref_workprofile_intro">当为您的工作场所或教育机构设置工作资料时,设置程序可能会尝试连接到 Google 以允许下载应用到工作资料。</string>
<string name="service_name_work_profile">工作资料</string>
<string name="pref_workprofile_disclaimer">您应自行确保使用 microG 符合企业的规章政策。microG 是在尽最大努力基础上提供的,不能保证完全按预期运行。</string>

<string name="pref_app_install_settings_title">商店安装设置</string>
<string name="pref_app_install_switch_title">允许安装渠道应用</string>
<string name="pref_app_install_other_apps_note">授权允许安装从其他渠道下载的应用程序。</string>
<string name="pref_app_install_permission_instruction">为确保您的应用程序正常运行,请授权安装从其他来源下载的应用程序。该应用程序的某些服务需要必要的权限才能运行,拒绝权限可能会限制或禁用该应用程序的功能。</string>
<string name="prefcat_app_install_list_title">授权渠道</string>
</resources>
6 changes: 6 additions & 0 deletions play-services-core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,12 @@ Please set up a password, PIN, or pattern lock screen."</string>
<string name="camera_permission_dialog_message">microG services needs to access your device\'s camera to scan a code for %1$s.\n\nTo enable, please grant camera permission to microG services in Settings.</string>
<string name="camera_permission_dialog_title">Camera permission required</string>

<string name="pref_app_install_settings_title">App Installer Settings</string>
<string name="pref_app_install_switch_title">Allow App Installation</string>
<string name="pref_app_install_other_apps_note">Authorization allows installation of apps provided from other sources.</string>
<string name="pref_app_install_permission_instruction">To ensure that your installed apps work properly, please authorize microG Companion to install apps downloaded from other sources.</string>
<string name="prefcat_app_install_list_title">Apps using App Installer</string>

<string name="service_name_google_location_sharing">Google Location Sharing</string>
<string name="location_sharing_description">Manage your real-time Location sharing across Google apps and services from this device</string>
<string name="location_sharing_learn_more">Learn more about &quot;Location Sharing&quot;</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<org.microg.gms.ui.SwitchBarPreference
android:title="@string/pref_app_install_switch_title"
android:key="pref_vending_allow_install_apps"
android:persistent="false" />

<PreferenceCategory android:layout="@layout/preference_category_no_label">
<org.microg.gms.ui.TextPreference
android:key="pref_permissions_titles"
android:selectable="false"
app:iconSpaceReserved="false"
android:summary="@string/pref_app_install_other_apps_note" />

<org.microg.gms.ui.TextPreference
android:icon="@drawable/ic_circle_warn"
android:selectable="false"
android:summary="@string/pref_app_install_permission_instruction" />
</PreferenceCategory>

<PreferenceCategory
android:key="pref_permission_installer_settings"
android:title="@string/prefcat_app_install_list_title"
app:allowDividerAbove="false"
app:allowDividerBelow="false"
app:iconSpaceReserved="false">
<Preference
android:enabled="false"
android:key="pref_permission_installer_none"
app:iconSpaceReserved="true"
android:title="@string/list_no_item_none" />
</PreferenceCategory>

</PreferenceScreen>
22 changes: 22 additions & 0 deletions vending-app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
android:name="android.permission.GET_ACCOUNTS"
android:maxSdkVersion="22" />
<uses-permission android:name="org.microg.gms.permission.READ_SETTINGS" />
<uses-permission android:name="org.microg.gms.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="com.google.android.gms.permission.READ_SETTINGS" />
<uses-permission android:name="com.google.android.gms.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
Expand Down Expand Up @@ -297,5 +299,25 @@
</intent-filter>
</service>

<activity
android:name="org.microg.vending.installer.AppInstallActivity"
android:exported="true"
android:excludeFromRecents="true"
android:theme="@style/Theme.App.Translucent"
tools:targetApi="21">
<intent-filter>
<action android:name="org.microg.vending.action.INSTALL_PACKAGE"/>
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/vnd.android.package-archive" />
</intent-filter>
</activity>

<activity
android:name="org.microg.vending.installer.AskInstallReminderActivity"
android:exported="false"
android:excludeFromRecents="true"
android:theme="@style/Theme.AppCompat.NoActionBar"
tools:targetApi="21"/>

</application>
</manifest>
Loading