Loading play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt +118 −31 Original line number Diff line number Diff line Loading @@ -7,10 +7,13 @@ package org.microg.gms.profile import android.annotation.SuppressLint import android.content.Context import android.content.res.XmlResourceParser import android.util.Log import org.microg.gms.settings.SettingsContract import org.microg.gms.settings.SettingsContract.Profile import org.microg.gms.utils.FileXmlResourceParser import org.xmlpull.v1.XmlPullParser import java.io.File import java.util.* import kotlin.random.Random Loading @@ -19,18 +22,67 @@ object ProfileManager { const val PROFILE_REAL = "real" const val PROFILE_AUTO = "auto" const val PROFILE_NATIVE = "native" const val PROFILE_USER = "user" const val PROFILE_SYSTEM = "system" private var initialized = false private var activeProfile: String? = null private fun getProfileFromSettings(context: Context) = SettingsContract.getSettings(context, Profile.getContentUri(context), arrayOf(Profile.PROFILE)) { it.getString(0) } private fun getAutoProfile(context: Context): String { private fun getUserProfileFile(context: Context): File = File(context.filesDir, "device_profile.xml") private fun getSystemProfileFile(context: Context): File = File("/system/etc/microg_device_profile.xml") private fun getProfileResId(context: Context, profile: String) = context.resources.getIdentifier("${context.packageName}:xml/profile_$profile".toLowerCase(Locale.US), null, null) fun getConfiguredProfile(context: Context): String = SettingsContract.getSettings(context, Profile.getContentUri(context), arrayOf(Profile.PROFILE)) { it.getString(0) } ?: PROFILE_AUTO fun getAutoProfile(context: Context): String { if (hasProfile(context, PROFILE_SYSTEM) && isAutoProfile(context, PROFILE_SYSTEM)) return PROFILE_SYSTEM val profile = "${android.os.Build.PRODUCT}_${android.os.Build.VERSION.SDK_INT}" if (hasProfile(context, profile)) return profile if (hasProfile(context, profile) && isAutoProfile(context, profile)) return profile return PROFILE_NATIVE } private fun getProfileResId(context: Context, profile: String) = context.resources.getIdentifier("${context.packageName}:xml/profile_$profile".toLowerCase(Locale.US), null, null) private fun hasProfile(context: Context, profile: String): Boolean = getProfileResId(context, profile) != 0 fun hasProfile(context: Context, profile: String): Boolean = when (profile) { PROFILE_AUTO -> hasProfile(context, getAutoProfile(context)) PROFILE_NATIVE, PROFILE_REAL -> true PROFILE_USER -> getUserProfileFile(context).exists() PROFILE_SYSTEM -> getSystemProfileFile(context).exists() else -> getProfileResId(context, profile) != 0 } private fun getProfileXml(context: Context, profile: String): XmlResourceParser? = kotlin.runCatching { when (profile) { PROFILE_AUTO -> getProfileXml(context, getAutoProfile(context)) PROFILE_NATIVE, PROFILE_REAL -> null PROFILE_USER -> FileXmlResourceParser(getUserProfileFile(context)) PROFILE_SYSTEM -> FileXmlResourceParser(getSystemProfileFile(context)) else -> { val profileResId = getProfileResId(context, profile) if (profileResId == 0) return@runCatching null context.resources.getXml(profileResId) } } }.getOrNull() fun isAutoProfile(context: Context, profile: String): Boolean = kotlin.runCatching { when (profile) { PROFILE_AUTO -> false PROFILE_REAL -> false PROFILE_NATIVE -> true else -> getProfileXml(context, profile)?.use { var next = it.next() while (next != XmlPullParser.END_DOCUMENT) { when (next) { XmlPullParser.START_TAG -> when (it.name) { "profile" -> { return@use it.getAttributeBooleanValue(null, "auto", false) } } } next = it.next() } } == true } }.getOrDefault(false) private fun getProfileData(context: Context, profile: String, realData: Map<String, String>): Map<String, String> { try { if (profile in listOf(PROFILE_REAL, PROFILE_NATIVE)) return realData Loading @@ -38,7 +90,7 @@ object ProfileManager { if (profileResId == 0) return realData val resultData = mutableMapOf<String, String>() resultData.putAll(realData) context.resources.getXml(profileResId).use { getProfileXml(context, profile)?.use { var next = it.next() while (next != XmlPullParser.END_DOCUMENT) { when (next) { Loading @@ -61,7 +113,7 @@ object ProfileManager { } } private fun getActiveProfile(context: Context) = getProfileFromSettings(context).let { if (it != PROFILE_AUTO) it else getAutoProfile(context) } private fun getProfile(context: Context) = getConfiguredProfile(context).let { if (it != PROFILE_AUTO) it else getAutoProfile(context) } private fun getSerialFromSettings(context: Context): String? = SettingsContract.getSettings(context, Profile.getContentUri(context), arrayOf(Profile.SERIAL)) { it.getString(0) } private fun saveSerial(context: Context, serial: String) = SettingsContract.setSettings(context, Profile.getContentUri(context)) { put(Profile.SERIAL, serial) } Loading Loading @@ -99,9 +151,7 @@ object ProfileManager { // From profile try { val profileResId = getProfileResId(context, profile) if (profileResId != 0) { context.resources.getXml(profileResId).use { getProfileXml(context, profile)?.use { var next = it.next() while (next != XmlPullParser.END_DOCUMENT) { when (next) { Loading @@ -112,24 +162,23 @@ object ProfileManager { next = it.next() } } } } catch (e: Exception) { Log.w(TAG, e) } // Fallback return "008741A0B2C4D6E8" return randomSerial("008741A0B2C4D6E8") } @SuppressLint("MissingPermission") private fun getEffectiveProfileSerial(context: Context, profile: String): String { getSerialFromSettings(context)?.let { return it } fun getSerial(context: Context, profile: String = getProfile(context), local: Boolean = false): String { if (!local) getSerialFromSettings(context)?.let { return it } val serialTemplate = getProfileSerialTemplate(context, profile) val serial = when { profile == PROFILE_REAL && serialTemplate != android.os.Build.UNKNOWN -> serialTemplate else -> randomSerial(serialTemplate) } saveSerial(context, serial) if (!local) saveSerial(context, serial) return serial } Loading Loading @@ -210,29 +259,67 @@ object ProfileManager { } } private fun applyProfile(context: Context, profile: String) { val profileData = getProfileData(context, profile, getRealData()) ?: getRealData() private fun applyProfile(context: Context, profile: String, serial: String = getSerial(context, profile)) { val profileData = getProfileData(context, profile, getRealData()) if (Log.isLoggable(TAG, Log.VERBOSE)) { for ((key, value) in profileData) { Log.v(TAG, "<data key=\"$key\" value=\"$value\" />") } } applyProfileData(profileData) Build.SERIAL = getEffectiveProfileSerial(context, profile) Build.SERIAL = serial Log.d(TAG, "Using Serial ${Build.SERIAL}") activeProfile = profile } fun getProfileName(context: Context, profile: String): String? = getProfileName { getProfileXml(context, profile) } private fun getProfileName(parserCreator: () -> XmlResourceParser?): String? = parserCreator()?.use { var next = it.next() while (next != XmlPullParser.END_DOCUMENT) { when (next) { XmlPullParser.START_TAG -> when (it.name) { "profile" -> { return@use it.getAttributeValue(null, "name") } } } next = it.next() } null } fun setProfile(context: Context, profile: String?) { val changed = getProfile(context) != profile val newProfile = profile ?: PROFILE_AUTO val newSerial = if (changed) getSerial(context, newProfile, true) else getSerial(context) SettingsContract.setSettings(context, Profile.getContentUri(context)) { put(Profile.PROFILE, profile) put(Profile.SERIAL, null as String?) put(Profile.PROFILE, newProfile) if (changed) put(Profile.SERIAL, newSerial) } if (changed && activeProfile != null) applyProfile(context, newProfile, newSerial) } fun importUserProfile(context: Context, file: File): Boolean { val profileName = getProfileName { FileXmlResourceParser(file) } ?: return false try { Log.d(TAG, "Importing user profile '$profileName'") file.copyTo(getUserProfileFile(context)) if (activeProfile == PROFILE_USER) applyProfile(context, PROFILE_USER) return true } catch (e: Exception) { Log.w(TAG, e) return false } applyProfile(context, profile ?: PROFILE_AUTO) } @JvmStatic fun ensureInitialized(context: Context) { synchronized(this) { if (initialized) return try { val profile = getActiveProfile(context) val profile = getProfile(context) if (activeProfile == profile) return applyProfile(context, profile) initialized = true } catch (e: Exception) { Log.w(TAG, e) } Loading play-services-base-core/src/main/kotlin/org/microg/gms/utils/FileXmlResourceParser.kt 0 → 100644 +127 −0 Original line number Diff line number Diff line /* * SPDX-FileCopyrightText: 2022 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ package org.microg.gms.utils import android.content.res.XmlResourceParser import android.util.Xml import org.xmlpull.v1.XmlPullParser import java.io.Closeable import java.io.File import java.io.FileReader import java.io.Reader class FileXmlResourceParser(private val reader: Reader, private val parser: XmlPullParser = Xml.newPullParser()) : XmlResourceParser, XmlPullParser by parser, Closeable by reader { constructor(file: File) : this(FileReader(file)) init { parser.setInput(reader) } override fun getAttributeNameResource(index: Int): Int { return 0 } override fun getAttributeListValue( namespace: String?, attribute: String?, options: Array<String?>?, defaultValue: Int ): Int { val s = getAttributeValue(namespace, attribute) return s?.toInt() ?: defaultValue } override fun getAttributeBooleanValue( namespace: String?, attribute: String?, defaultValue: Boolean ): Boolean { val s = getAttributeValue(namespace, attribute) return s?.toBooleanStrictOrNull() ?: defaultValue } override fun getAttributeResourceValue( namespace: String?, attribute: String?, defaultValue: Int ): Int { val s = getAttributeValue(namespace, attribute) return s?.toInt() ?: defaultValue } override fun getAttributeIntValue( namespace: String?, attribute: String?, defaultValue: Int ): Int { val s = getAttributeValue(namespace, attribute) return s?.toInt() ?: defaultValue } override fun getAttributeUnsignedIntValue( namespace: String?, attribute: String?, defaultValue: Int ): Int { val s = getAttributeValue(namespace, attribute) return s?.toInt() ?: defaultValue } override fun getAttributeFloatValue( namespace: String?, attribute: String?, defaultValue: Float ): Float { val s = getAttributeValue(namespace, attribute) return s?.toFloat() ?: defaultValue } override fun getAttributeListValue( index: Int, options: Array<String?>?, defaultValue: Int ): Int { val s = getAttributeValue(index) return s?.toInt() ?: defaultValue } override fun getAttributeBooleanValue(index: Int, defaultValue: Boolean): Boolean { val s = getAttributeValue(index) return s?.toBooleanStrictOrNull() ?: defaultValue } override fun getAttributeResourceValue(index: Int, defaultValue: Int): Int { val s = getAttributeValue(index) return s?.toInt() ?: defaultValue } override fun getAttributeIntValue(index: Int, defaultValue: Int): Int { val s = getAttributeValue(index) return s?.toInt() ?: defaultValue } override fun getAttributeUnsignedIntValue(index: Int, defaultValue: Int): Int { val s = getAttributeValue(index) return s?.toInt() ?: defaultValue } override fun getAttributeFloatValue(index: Int, defaultValue: Float): Float { val s = getAttributeValue(index) return s?.toFloat() ?: defaultValue } override fun getIdAttribute(): String? { return getAttributeValue(null, "id") } override fun getClassAttribute(): String? { return getAttributeValue(null, "class") } override fun getIdAttributeResourceValue(defaultValue: Int): Int { return getAttributeResourceValue(null, "id", defaultValue) } override fun getStyleAttribute(): Int { return getAttributeResourceValue(null, "style", 0) } } play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt +98 −1 Original line number Diff line number Diff line Loading @@ -5,31 +5,122 @@ package org.microg.gms.ui import android.net.Uri import android.os.Bundle import android.os.Handler import android.text.format.DateUtils import android.util.Log import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.lifecycle.lifecycleScope import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import com.google.android.gms.R import org.microg.gms.checkin.getCheckinServiceInfo import org.microg.gms.profile.ProfileManager import org.microg.gms.profile.ProfileManager.PROFILE_AUTO import org.microg.gms.profile.ProfileManager.PROFILE_NATIVE import org.microg.gms.profile.ProfileManager.PROFILE_REAL import org.microg.gms.profile.ProfileManager.PROFILE_SYSTEM import org.microg.gms.profile.ProfileManager.PROFILE_USER import java.io.File import java.io.FileOutputStream class DeviceRegistrationPreferencesFragment : PreferenceFragmentCompat() { private lateinit var deviceProfile: ListPreference private lateinit var importProfile: Preference private lateinit var serial: Preference private lateinit var statusCategory: PreferenceCategory private lateinit var status: Preference private lateinit var androidId: Preference private val handler = Handler() private val updateRunnable = Runnable { updateStatus() } private lateinit var profileFileImport: ActivityResultLauncher<String> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) profileFileImport = registerForActivityResult(ActivityResultContracts.GetContent(), this::onFileSelected) } private fun onFileSelected(uri: Uri?) { if (uri == null) return try { val context = requireContext() val file = File.createTempFile("profile_", ".xml", context.cacheDir) context.contentResolver.openInputStream(uri)?.use { inputStream -> FileOutputStream(file).use { inputStream.copyTo(it) } } val success = ProfileManager.importUserProfile(context, file) file.delete() if (success && ProfileManager.isAutoProfile(context, PROFILE_USER)) { ProfileManager.setProfile(context, PROFILE_USER) } updateStatus() } catch (e: Exception) { Log.w(TAG, e) } } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_device_registration) } override fun onBindPreferences() { deviceProfile = preferenceScreen.findPreference("pref_device_profile") ?: deviceProfile importProfile = preferenceScreen.findPreference("pref_device_profile_import") ?: importProfile serial = preferenceScreen.findPreference("pref_device_serial") ?: serial statusCategory = preferenceScreen.findPreference("prefcat_device_registration_status") ?: statusCategory status = preferenceScreen.findPreference("pref_device_registration_status") ?: status androidId = preferenceScreen.findPreference("pref_device_registration_android_id") ?: androidId deviceProfile.setOnPreferenceChangeListener { _, newValue -> ProfileManager.setProfile(requireContext(), newValue as String? ?: PROFILE_AUTO) updateStatus() true } importProfile.setOnPreferenceClickListener { profileFileImport.launch("text/xml") true } } private fun configureProfilePreference() { val context = requireContext() val configuredProfile = ProfileManager.getConfiguredProfile(context) val autoProfile = ProfileManager.getAutoProfile(context) val autoProfileName = when (autoProfile) { PROFILE_NATIVE -> "Native" PROFILE_REAL -> "Real" else -> ProfileManager.getProfileName(context, autoProfile) } val profiles = mutableListOf(PROFILE_AUTO, PROFILE_NATIVE, PROFILE_REAL) val profileNames = mutableListOf("Automatic: $autoProfileName", "Native", "Real") if (ProfileManager.hasProfile(context, PROFILE_SYSTEM)) { profiles.add(PROFILE_SYSTEM) profileNames.add("System: ${ProfileManager.getProfileName(context, PROFILE_SYSTEM)}") } if (ProfileManager.hasProfile(context, PROFILE_USER)) { profiles.add(PROFILE_USER) profileNames.add("Custom: ${ProfileManager.getProfileName(context, PROFILE_USER)}") } for (profile in R.xml::class.java.declaredFields.map { it.name } .filter { it.startsWith("profile_") } .map { it.substring(8) } .sorted()) { val profileName = ProfileManager.getProfileName(context, profile) if (profileName != null) { profiles.add(profile) profileNames.add(profileName) } } deviceProfile.entryValues = profiles.toTypedArray() deviceProfile.entries = profileNames.toTypedArray() deviceProfile.value = configuredProfile deviceProfile.summary = profiles.indexOf(configuredProfile).takeIf { it >= 0 }?.let { profileNames[it] } ?: "Unknown" } override fun onResume() { Loading @@ -43,13 +134,19 @@ class DeviceRegistrationPreferencesFragment : PreferenceFragmentCompat() { } private fun updateStatus() { handler.removeCallbacks(updateRunnable) handler.postDelayed(updateRunnable, UPDATE_INTERVAL) val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { configureProfilePreference() serial.summary = ProfileManager.getSerial(appContext) val serviceInfo = getCheckinServiceInfo(appContext) statusCategory.isVisible = serviceInfo.configuration.enabled if (serviceInfo.lastCheckin > 0) { status.summary = getString(R.string.checkin_last_registration, DateUtils.getRelativeTimeSpanString(serviceInfo.lastCheckin, System.currentTimeMillis(), 0)) status.summary = getString( R.string.checkin_last_registration, DateUtils.getRelativeTimeSpanString(serviceInfo.lastCheckin, System.currentTimeMillis(), 0) ) androidId.isVisible = true androidId.summary = serviceInfo.androidId.toString(16) } else { Loading play-services-core/src/main/res/xml/preferences_device_registration.xml +7 −1 Original line number Diff line number Diff line Loading @@ -11,12 +11,18 @@ android:title="Device profile"> <ListPreference android:key="pref_device_profile" android:persistent="false" android:title="Select profile" tools:summary="Automatic (Google Pixel 3, Android 11)" /> <Preference android:key="pref_device_profile_import" android:summary="Import device profile from file" android:title="Import profile" /> android:title="Import custom profile" /> <Preference android:enabled="false" android:key="pref_device_serial" android:title="Serial" tools:summary="123456" /> </PreferenceCategory> <PreferenceCategory android:key="prefcat_device_registration_status" Loading play-services-core/src/main/res/xml/profile_bullhead_27.xml +3 −2 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ SPDX-FileCopyrightText: 2021, microG Project Team ~ SPDX-FileCopyrightText: 2021 microG Project Team ~ SPDX-License-Identifier: Apache-2.0 --> <profile name="Google Nexus 5X (Android 8.1.0)" product="bullhead" sdk="27" id="bullhead_27"> <profile name="Google Nexus 5X (Android 8.1.0)" product="bullhead" sdk="27" id="bullhead_27" auto="true"> <!-- Data from OPM3.171019.016, Mar 2018 --> <data key="Build.BOARD" value="bullhead" /> <data key="Build.BOOTLOADER" value="BHZ31b" /> Loading @@ -27,6 +27,7 @@ <data key="Build.VERSION.CODENAME" value="REL" /> <data key="Build.VERSION.INCREMENTAL" value="6d95f5a143" /> <data key="Build.VERSION.RELEASE" value="8.1.0" /> <data key="Build.VERSION.SECURITY_PATCH" value="2021-10-05" /> <data key="Build.VERSION.SDK" value="27" /> <data key="Build.VERSION.SDK_INT" value="27" /> <data key="Build.SUPPORTED_ABIS" value="arm64-v8a,armeabi-v7a,armeabi" /> Loading Loading
play-services-base-core/src/main/kotlin/org/microg/gms/profile/ProfileManager.kt +118 −31 Original line number Diff line number Diff line Loading @@ -7,10 +7,13 @@ package org.microg.gms.profile import android.annotation.SuppressLint import android.content.Context import android.content.res.XmlResourceParser import android.util.Log import org.microg.gms.settings.SettingsContract import org.microg.gms.settings.SettingsContract.Profile import org.microg.gms.utils.FileXmlResourceParser import org.xmlpull.v1.XmlPullParser import java.io.File import java.util.* import kotlin.random.Random Loading @@ -19,18 +22,67 @@ object ProfileManager { const val PROFILE_REAL = "real" const val PROFILE_AUTO = "auto" const val PROFILE_NATIVE = "native" const val PROFILE_USER = "user" const val PROFILE_SYSTEM = "system" private var initialized = false private var activeProfile: String? = null private fun getProfileFromSettings(context: Context) = SettingsContract.getSettings(context, Profile.getContentUri(context), arrayOf(Profile.PROFILE)) { it.getString(0) } private fun getAutoProfile(context: Context): String { private fun getUserProfileFile(context: Context): File = File(context.filesDir, "device_profile.xml") private fun getSystemProfileFile(context: Context): File = File("/system/etc/microg_device_profile.xml") private fun getProfileResId(context: Context, profile: String) = context.resources.getIdentifier("${context.packageName}:xml/profile_$profile".toLowerCase(Locale.US), null, null) fun getConfiguredProfile(context: Context): String = SettingsContract.getSettings(context, Profile.getContentUri(context), arrayOf(Profile.PROFILE)) { it.getString(0) } ?: PROFILE_AUTO fun getAutoProfile(context: Context): String { if (hasProfile(context, PROFILE_SYSTEM) && isAutoProfile(context, PROFILE_SYSTEM)) return PROFILE_SYSTEM val profile = "${android.os.Build.PRODUCT}_${android.os.Build.VERSION.SDK_INT}" if (hasProfile(context, profile)) return profile if (hasProfile(context, profile) && isAutoProfile(context, profile)) return profile return PROFILE_NATIVE } private fun getProfileResId(context: Context, profile: String) = context.resources.getIdentifier("${context.packageName}:xml/profile_$profile".toLowerCase(Locale.US), null, null) private fun hasProfile(context: Context, profile: String): Boolean = getProfileResId(context, profile) != 0 fun hasProfile(context: Context, profile: String): Boolean = when (profile) { PROFILE_AUTO -> hasProfile(context, getAutoProfile(context)) PROFILE_NATIVE, PROFILE_REAL -> true PROFILE_USER -> getUserProfileFile(context).exists() PROFILE_SYSTEM -> getSystemProfileFile(context).exists() else -> getProfileResId(context, profile) != 0 } private fun getProfileXml(context: Context, profile: String): XmlResourceParser? = kotlin.runCatching { when (profile) { PROFILE_AUTO -> getProfileXml(context, getAutoProfile(context)) PROFILE_NATIVE, PROFILE_REAL -> null PROFILE_USER -> FileXmlResourceParser(getUserProfileFile(context)) PROFILE_SYSTEM -> FileXmlResourceParser(getSystemProfileFile(context)) else -> { val profileResId = getProfileResId(context, profile) if (profileResId == 0) return@runCatching null context.resources.getXml(profileResId) } } }.getOrNull() fun isAutoProfile(context: Context, profile: String): Boolean = kotlin.runCatching { when (profile) { PROFILE_AUTO -> false PROFILE_REAL -> false PROFILE_NATIVE -> true else -> getProfileXml(context, profile)?.use { var next = it.next() while (next != XmlPullParser.END_DOCUMENT) { when (next) { XmlPullParser.START_TAG -> when (it.name) { "profile" -> { return@use it.getAttributeBooleanValue(null, "auto", false) } } } next = it.next() } } == true } }.getOrDefault(false) private fun getProfileData(context: Context, profile: String, realData: Map<String, String>): Map<String, String> { try { if (profile in listOf(PROFILE_REAL, PROFILE_NATIVE)) return realData Loading @@ -38,7 +90,7 @@ object ProfileManager { if (profileResId == 0) return realData val resultData = mutableMapOf<String, String>() resultData.putAll(realData) context.resources.getXml(profileResId).use { getProfileXml(context, profile)?.use { var next = it.next() while (next != XmlPullParser.END_DOCUMENT) { when (next) { Loading @@ -61,7 +113,7 @@ object ProfileManager { } } private fun getActiveProfile(context: Context) = getProfileFromSettings(context).let { if (it != PROFILE_AUTO) it else getAutoProfile(context) } private fun getProfile(context: Context) = getConfiguredProfile(context).let { if (it != PROFILE_AUTO) it else getAutoProfile(context) } private fun getSerialFromSettings(context: Context): String? = SettingsContract.getSettings(context, Profile.getContentUri(context), arrayOf(Profile.SERIAL)) { it.getString(0) } private fun saveSerial(context: Context, serial: String) = SettingsContract.setSettings(context, Profile.getContentUri(context)) { put(Profile.SERIAL, serial) } Loading Loading @@ -99,9 +151,7 @@ object ProfileManager { // From profile try { val profileResId = getProfileResId(context, profile) if (profileResId != 0) { context.resources.getXml(profileResId).use { getProfileXml(context, profile)?.use { var next = it.next() while (next != XmlPullParser.END_DOCUMENT) { when (next) { Loading @@ -112,24 +162,23 @@ object ProfileManager { next = it.next() } } } } catch (e: Exception) { Log.w(TAG, e) } // Fallback return "008741A0B2C4D6E8" return randomSerial("008741A0B2C4D6E8") } @SuppressLint("MissingPermission") private fun getEffectiveProfileSerial(context: Context, profile: String): String { getSerialFromSettings(context)?.let { return it } fun getSerial(context: Context, profile: String = getProfile(context), local: Boolean = false): String { if (!local) getSerialFromSettings(context)?.let { return it } val serialTemplate = getProfileSerialTemplate(context, profile) val serial = when { profile == PROFILE_REAL && serialTemplate != android.os.Build.UNKNOWN -> serialTemplate else -> randomSerial(serialTemplate) } saveSerial(context, serial) if (!local) saveSerial(context, serial) return serial } Loading Loading @@ -210,29 +259,67 @@ object ProfileManager { } } private fun applyProfile(context: Context, profile: String) { val profileData = getProfileData(context, profile, getRealData()) ?: getRealData() private fun applyProfile(context: Context, profile: String, serial: String = getSerial(context, profile)) { val profileData = getProfileData(context, profile, getRealData()) if (Log.isLoggable(TAG, Log.VERBOSE)) { for ((key, value) in profileData) { Log.v(TAG, "<data key=\"$key\" value=\"$value\" />") } } applyProfileData(profileData) Build.SERIAL = getEffectiveProfileSerial(context, profile) Build.SERIAL = serial Log.d(TAG, "Using Serial ${Build.SERIAL}") activeProfile = profile } fun getProfileName(context: Context, profile: String): String? = getProfileName { getProfileXml(context, profile) } private fun getProfileName(parserCreator: () -> XmlResourceParser?): String? = parserCreator()?.use { var next = it.next() while (next != XmlPullParser.END_DOCUMENT) { when (next) { XmlPullParser.START_TAG -> when (it.name) { "profile" -> { return@use it.getAttributeValue(null, "name") } } } next = it.next() } null } fun setProfile(context: Context, profile: String?) { val changed = getProfile(context) != profile val newProfile = profile ?: PROFILE_AUTO val newSerial = if (changed) getSerial(context, newProfile, true) else getSerial(context) SettingsContract.setSettings(context, Profile.getContentUri(context)) { put(Profile.PROFILE, profile) put(Profile.SERIAL, null as String?) put(Profile.PROFILE, newProfile) if (changed) put(Profile.SERIAL, newSerial) } if (changed && activeProfile != null) applyProfile(context, newProfile, newSerial) } fun importUserProfile(context: Context, file: File): Boolean { val profileName = getProfileName { FileXmlResourceParser(file) } ?: return false try { Log.d(TAG, "Importing user profile '$profileName'") file.copyTo(getUserProfileFile(context)) if (activeProfile == PROFILE_USER) applyProfile(context, PROFILE_USER) return true } catch (e: Exception) { Log.w(TAG, e) return false } applyProfile(context, profile ?: PROFILE_AUTO) } @JvmStatic fun ensureInitialized(context: Context) { synchronized(this) { if (initialized) return try { val profile = getActiveProfile(context) val profile = getProfile(context) if (activeProfile == profile) return applyProfile(context, profile) initialized = true } catch (e: Exception) { Log.w(TAG, e) } Loading
play-services-base-core/src/main/kotlin/org/microg/gms/utils/FileXmlResourceParser.kt 0 → 100644 +127 −0 Original line number Diff line number Diff line /* * SPDX-FileCopyrightText: 2022 microG Project Team * SPDX-License-Identifier: Apache-2.0 */ package org.microg.gms.utils import android.content.res.XmlResourceParser import android.util.Xml import org.xmlpull.v1.XmlPullParser import java.io.Closeable import java.io.File import java.io.FileReader import java.io.Reader class FileXmlResourceParser(private val reader: Reader, private val parser: XmlPullParser = Xml.newPullParser()) : XmlResourceParser, XmlPullParser by parser, Closeable by reader { constructor(file: File) : this(FileReader(file)) init { parser.setInput(reader) } override fun getAttributeNameResource(index: Int): Int { return 0 } override fun getAttributeListValue( namespace: String?, attribute: String?, options: Array<String?>?, defaultValue: Int ): Int { val s = getAttributeValue(namespace, attribute) return s?.toInt() ?: defaultValue } override fun getAttributeBooleanValue( namespace: String?, attribute: String?, defaultValue: Boolean ): Boolean { val s = getAttributeValue(namespace, attribute) return s?.toBooleanStrictOrNull() ?: defaultValue } override fun getAttributeResourceValue( namespace: String?, attribute: String?, defaultValue: Int ): Int { val s = getAttributeValue(namespace, attribute) return s?.toInt() ?: defaultValue } override fun getAttributeIntValue( namespace: String?, attribute: String?, defaultValue: Int ): Int { val s = getAttributeValue(namespace, attribute) return s?.toInt() ?: defaultValue } override fun getAttributeUnsignedIntValue( namespace: String?, attribute: String?, defaultValue: Int ): Int { val s = getAttributeValue(namespace, attribute) return s?.toInt() ?: defaultValue } override fun getAttributeFloatValue( namespace: String?, attribute: String?, defaultValue: Float ): Float { val s = getAttributeValue(namespace, attribute) return s?.toFloat() ?: defaultValue } override fun getAttributeListValue( index: Int, options: Array<String?>?, defaultValue: Int ): Int { val s = getAttributeValue(index) return s?.toInt() ?: defaultValue } override fun getAttributeBooleanValue(index: Int, defaultValue: Boolean): Boolean { val s = getAttributeValue(index) return s?.toBooleanStrictOrNull() ?: defaultValue } override fun getAttributeResourceValue(index: Int, defaultValue: Int): Int { val s = getAttributeValue(index) return s?.toInt() ?: defaultValue } override fun getAttributeIntValue(index: Int, defaultValue: Int): Int { val s = getAttributeValue(index) return s?.toInt() ?: defaultValue } override fun getAttributeUnsignedIntValue(index: Int, defaultValue: Int): Int { val s = getAttributeValue(index) return s?.toInt() ?: defaultValue } override fun getAttributeFloatValue(index: Int, defaultValue: Float): Float { val s = getAttributeValue(index) return s?.toFloat() ?: defaultValue } override fun getIdAttribute(): String? { return getAttributeValue(null, "id") } override fun getClassAttribute(): String? { return getAttributeValue(null, "class") } override fun getIdAttributeResourceValue(defaultValue: Int): Int { return getAttributeResourceValue(null, "id", defaultValue) } override fun getStyleAttribute(): Int { return getAttributeResourceValue(null, "style", 0) } }
play-services-core/src/main/kotlin/org/microg/gms/ui/DeviceRegistrationPreferencesFragment.kt +98 −1 Original line number Diff line number Diff line Loading @@ -5,31 +5,122 @@ package org.microg.gms.ui import android.net.Uri import android.os.Bundle import android.os.Handler import android.text.format.DateUtils import android.util.Log import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.lifecycle.lifecycleScope import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat import com.google.android.gms.R import org.microg.gms.checkin.getCheckinServiceInfo import org.microg.gms.profile.ProfileManager import org.microg.gms.profile.ProfileManager.PROFILE_AUTO import org.microg.gms.profile.ProfileManager.PROFILE_NATIVE import org.microg.gms.profile.ProfileManager.PROFILE_REAL import org.microg.gms.profile.ProfileManager.PROFILE_SYSTEM import org.microg.gms.profile.ProfileManager.PROFILE_USER import java.io.File import java.io.FileOutputStream class DeviceRegistrationPreferencesFragment : PreferenceFragmentCompat() { private lateinit var deviceProfile: ListPreference private lateinit var importProfile: Preference private lateinit var serial: Preference private lateinit var statusCategory: PreferenceCategory private lateinit var status: Preference private lateinit var androidId: Preference private val handler = Handler() private val updateRunnable = Runnable { updateStatus() } private lateinit var profileFileImport: ActivityResultLauncher<String> override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) profileFileImport = registerForActivityResult(ActivityResultContracts.GetContent(), this::onFileSelected) } private fun onFileSelected(uri: Uri?) { if (uri == null) return try { val context = requireContext() val file = File.createTempFile("profile_", ".xml", context.cacheDir) context.contentResolver.openInputStream(uri)?.use { inputStream -> FileOutputStream(file).use { inputStream.copyTo(it) } } val success = ProfileManager.importUserProfile(context, file) file.delete() if (success && ProfileManager.isAutoProfile(context, PROFILE_USER)) { ProfileManager.setProfile(context, PROFILE_USER) } updateStatus() } catch (e: Exception) { Log.w(TAG, e) } } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_device_registration) } override fun onBindPreferences() { deviceProfile = preferenceScreen.findPreference("pref_device_profile") ?: deviceProfile importProfile = preferenceScreen.findPreference("pref_device_profile_import") ?: importProfile serial = preferenceScreen.findPreference("pref_device_serial") ?: serial statusCategory = preferenceScreen.findPreference("prefcat_device_registration_status") ?: statusCategory status = preferenceScreen.findPreference("pref_device_registration_status") ?: status androidId = preferenceScreen.findPreference("pref_device_registration_android_id") ?: androidId deviceProfile.setOnPreferenceChangeListener { _, newValue -> ProfileManager.setProfile(requireContext(), newValue as String? ?: PROFILE_AUTO) updateStatus() true } importProfile.setOnPreferenceClickListener { profileFileImport.launch("text/xml") true } } private fun configureProfilePreference() { val context = requireContext() val configuredProfile = ProfileManager.getConfiguredProfile(context) val autoProfile = ProfileManager.getAutoProfile(context) val autoProfileName = when (autoProfile) { PROFILE_NATIVE -> "Native" PROFILE_REAL -> "Real" else -> ProfileManager.getProfileName(context, autoProfile) } val profiles = mutableListOf(PROFILE_AUTO, PROFILE_NATIVE, PROFILE_REAL) val profileNames = mutableListOf("Automatic: $autoProfileName", "Native", "Real") if (ProfileManager.hasProfile(context, PROFILE_SYSTEM)) { profiles.add(PROFILE_SYSTEM) profileNames.add("System: ${ProfileManager.getProfileName(context, PROFILE_SYSTEM)}") } if (ProfileManager.hasProfile(context, PROFILE_USER)) { profiles.add(PROFILE_USER) profileNames.add("Custom: ${ProfileManager.getProfileName(context, PROFILE_USER)}") } for (profile in R.xml::class.java.declaredFields.map { it.name } .filter { it.startsWith("profile_") } .map { it.substring(8) } .sorted()) { val profileName = ProfileManager.getProfileName(context, profile) if (profileName != null) { profiles.add(profile) profileNames.add(profileName) } } deviceProfile.entryValues = profiles.toTypedArray() deviceProfile.entries = profileNames.toTypedArray() deviceProfile.value = configuredProfile deviceProfile.summary = profiles.indexOf(configuredProfile).takeIf { it >= 0 }?.let { profileNames[it] } ?: "Unknown" } override fun onResume() { Loading @@ -43,13 +134,19 @@ class DeviceRegistrationPreferencesFragment : PreferenceFragmentCompat() { } private fun updateStatus() { handler.removeCallbacks(updateRunnable) handler.postDelayed(updateRunnable, UPDATE_INTERVAL) val appContext = requireContext().applicationContext lifecycleScope.launchWhenResumed { configureProfilePreference() serial.summary = ProfileManager.getSerial(appContext) val serviceInfo = getCheckinServiceInfo(appContext) statusCategory.isVisible = serviceInfo.configuration.enabled if (serviceInfo.lastCheckin > 0) { status.summary = getString(R.string.checkin_last_registration, DateUtils.getRelativeTimeSpanString(serviceInfo.lastCheckin, System.currentTimeMillis(), 0)) status.summary = getString( R.string.checkin_last_registration, DateUtils.getRelativeTimeSpanString(serviceInfo.lastCheckin, System.currentTimeMillis(), 0) ) androidId.isVisible = true androidId.summary = serviceInfo.androidId.toString(16) } else { Loading
play-services-core/src/main/res/xml/preferences_device_registration.xml +7 −1 Original line number Diff line number Diff line Loading @@ -11,12 +11,18 @@ android:title="Device profile"> <ListPreference android:key="pref_device_profile" android:persistent="false" android:title="Select profile" tools:summary="Automatic (Google Pixel 3, Android 11)" /> <Preference android:key="pref_device_profile_import" android:summary="Import device profile from file" android:title="Import profile" /> android:title="Import custom profile" /> <Preference android:enabled="false" android:key="pref_device_serial" android:title="Serial" tools:summary="123456" /> </PreferenceCategory> <PreferenceCategory android:key="prefcat_device_registration_status" Loading
play-services-core/src/main/res/xml/profile_bullhead_27.xml +3 −2 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- ~ SPDX-FileCopyrightText: 2021, microG Project Team ~ SPDX-FileCopyrightText: 2021 microG Project Team ~ SPDX-License-Identifier: Apache-2.0 --> <profile name="Google Nexus 5X (Android 8.1.0)" product="bullhead" sdk="27" id="bullhead_27"> <profile name="Google Nexus 5X (Android 8.1.0)" product="bullhead" sdk="27" id="bullhead_27" auto="true"> <!-- Data from OPM3.171019.016, Mar 2018 --> <data key="Build.BOARD" value="bullhead" /> <data key="Build.BOOTLOADER" value="BHZ31b" /> Loading @@ -27,6 +27,7 @@ <data key="Build.VERSION.CODENAME" value="REL" /> <data key="Build.VERSION.INCREMENTAL" value="6d95f5a143" /> <data key="Build.VERSION.RELEASE" value="8.1.0" /> <data key="Build.VERSION.SECURITY_PATCH" value="2021-10-05" /> <data key="Build.VERSION.SDK" value="27" /> <data key="Build.VERSION.SDK_INT" value="27" /> <data key="Build.SUPPORTED_ABIS" value="arm64-v8a,armeabi-v7a,armeabi" /> Loading