Profile Manager: Add configuration features

This commit is contained in:
Marvin W 2022-01-22 21:44:05 +01:00
parent 3bbae67fda
commit 6e21b52bfe
No known key found for this signature in database
GPG Key ID: 072E9235DB996F2A
6 changed files with 389 additions and 35 deletions

View File

@ -7,10 +7,13 @@ package org.microg.gms.profile
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.res.XmlResourceParser
import android.util.Log import android.util.Log
import org.microg.gms.settings.SettingsContract import org.microg.gms.settings.SettingsContract
import org.microg.gms.settings.SettingsContract.Profile import org.microg.gms.settings.SettingsContract.Profile
import org.microg.gms.utils.FileXmlResourceParser
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
import java.io.File
import java.util.* import java.util.*
import kotlin.random.Random import kotlin.random.Random
@ -19,18 +22,67 @@ object ProfileManager {
const val PROFILE_REAL = "real" const val PROFILE_REAL = "real"
const val PROFILE_AUTO = "auto" const val PROFILE_AUTO = "auto"
const val PROFILE_NATIVE = "native" 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 getUserProfileFile(context: Context): File = File(context.filesDir, "device_profile.xml")
private fun getAutoProfile(context: Context): String { 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}" 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 return PROFILE_NATIVE
} }
private fun getProfileResId(context: Context, profile: String) = context.resources.getIdentifier("${context.packageName}:xml/profile_$profile".toLowerCase(Locale.US), null, null) fun hasProfile(context: Context, profile: String): Boolean = when (profile) {
private fun hasProfile(context: Context, profile: String): Boolean = getProfileResId(context, profile) != 0 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> { private fun getProfileData(context: Context, profile: String, realData: Map<String, String>): Map<String, String> {
try { try {
if (profile in listOf(PROFILE_REAL, PROFILE_NATIVE)) return realData if (profile in listOf(PROFILE_REAL, PROFILE_NATIVE)) return realData
@ -38,7 +90,7 @@ object ProfileManager {
if (profileResId == 0) return realData if (profileResId == 0) return realData
val resultData = mutableMapOf<String, String>() val resultData = mutableMapOf<String, String>()
resultData.putAll(realData) resultData.putAll(realData)
context.resources.getXml(profileResId).use { getProfileXml(context, profile)?.use {
var next = it.next() var next = it.next()
while (next != XmlPullParser.END_DOCUMENT) { while (next != XmlPullParser.END_DOCUMENT) {
when (next) { when (next) {
@ -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 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) } private fun saveSerial(context: Context, serial: String) = SettingsContract.setSettings(context, Profile.getContentUri(context)) { put(Profile.SERIAL, serial) }
@ -99,18 +151,15 @@ object ProfileManager {
// From profile // From profile
try { try {
val profileResId = getProfileResId(context, profile) getProfileXml(context, profile)?.use {
if (profileResId != 0) { var next = it.next()
context.resources.getXml(profileResId).use { while (next != XmlPullParser.END_DOCUMENT) {
var next = it.next() when (next) {
while (next != XmlPullParser.END_DOCUMENT) { XmlPullParser.START_TAG -> when (it.name) {
when (next) { "serial" -> return it.getAttributeValue(null, "template")
XmlPullParser.START_TAG -> when (it.name) {
"serial" -> return it.getAttributeValue(null, "template")
}
} }
next = it.next()
} }
next = it.next()
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -118,18 +167,18 @@ object ProfileManager {
} }
// Fallback // Fallback
return "008741A0B2C4D6E8" return randomSerial("008741A0B2C4D6E8")
} }
@SuppressLint("MissingPermission") @SuppressLint("MissingPermission")
private fun getEffectiveProfileSerial(context: Context, profile: String): String { fun getSerial(context: Context, profile: String = getProfile(context), local: Boolean = false): String {
getSerialFromSettings(context)?.let { return it } if (!local) getSerialFromSettings(context)?.let { return it }
val serialTemplate = getProfileSerialTemplate(context, profile) val serialTemplate = getProfileSerialTemplate(context, profile)
val serial = when { val serial = when {
profile == PROFILE_REAL && serialTemplate != android.os.Build.UNKNOWN -> serialTemplate profile == PROFILE_REAL && serialTemplate != android.os.Build.UNKNOWN -> serialTemplate
else -> randomSerial(serialTemplate) else -> randomSerial(serialTemplate)
} }
saveSerial(context, serial) if (!local) saveSerial(context, serial)
return serial return serial
} }
@ -210,29 +259,67 @@ object ProfileManager {
} }
} }
private fun applyProfile(context: Context, profile: String) { private fun applyProfile(context: Context, profile: String, serial: String = getSerial(context, profile)) {
val profileData = getProfileData(context, profile, getRealData()) ?: getRealData() 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) applyProfileData(profileData)
Build.SERIAL = getEffectiveProfileSerial(context, profile) Build.SERIAL = serial
Log.d(TAG, "Using Serial ${Build.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?) { 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)) { SettingsContract.setSettings(context, Profile.getContentUri(context)) {
put(Profile.PROFILE, profile) put(Profile.PROFILE, newProfile)
put(Profile.SERIAL, null as String?) 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 @JvmStatic
fun ensureInitialized(context: Context) { fun ensureInitialized(context: Context) {
synchronized(this) { synchronized(this) {
if (initialized) return
try { try {
val profile = getActiveProfile(context) val profile = getProfile(context)
if (activeProfile == profile) return
applyProfile(context, profile) applyProfile(context, profile)
initialized = true
} catch (e: Exception) { } catch (e: Exception) {
Log.w(TAG, e) Log.w(TAG, e)
} }

View File

@ -0,0 +1,127 @@
/*
* 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)
}
}

View File

@ -5,31 +5,122 @@
package org.microg.gms.ui package org.microg.gms.ui
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.text.format.DateUtils 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.lifecycle.lifecycleScope
import androidx.preference.ListPreference
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceCategory import androidx.preference.PreferenceCategory
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import com.google.android.gms.R import com.google.android.gms.R
import org.microg.gms.checkin.getCheckinServiceInfo 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() { 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 statusCategory: PreferenceCategory
private lateinit var status: Preference private lateinit var status: Preference
private lateinit var androidId: Preference private lateinit var androidId: Preference
private val handler = Handler() private val handler = Handler()
private val updateRunnable = Runnable { updateStatus() } 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?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.preferences_device_registration) addPreferencesFromResource(R.xml.preferences_device_registration)
} }
override fun onBindPreferences() { 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 statusCategory = preferenceScreen.findPreference("prefcat_device_registration_status") ?: statusCategory
status = preferenceScreen.findPreference("pref_device_registration_status") ?: status status = preferenceScreen.findPreference("pref_device_registration_status") ?: status
androidId = preferenceScreen.findPreference("pref_device_registration_android_id") ?: androidId 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() { override fun onResume() {
@ -43,13 +134,19 @@ class DeviceRegistrationPreferencesFragment : PreferenceFragmentCompat() {
} }
private fun updateStatus() { private fun updateStatus() {
handler.removeCallbacks(updateRunnable)
handler.postDelayed(updateRunnable, UPDATE_INTERVAL) handler.postDelayed(updateRunnable, UPDATE_INTERVAL)
val appContext = requireContext().applicationContext val appContext = requireContext().applicationContext
lifecycleScope.launchWhenResumed { lifecycleScope.launchWhenResumed {
configureProfilePreference()
serial.summary = ProfileManager.getSerial(appContext)
val serviceInfo = getCheckinServiceInfo(appContext) val serviceInfo = getCheckinServiceInfo(appContext)
statusCategory.isVisible = serviceInfo.configuration.enabled statusCategory.isVisible = serviceInfo.configuration.enabled
if (serviceInfo.lastCheckin > 0) { 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.isVisible = true
androidId.summary = serviceInfo.androidId.toString(16) androidId.summary = serviceInfo.androidId.toString(16)
} else { } else {

View File

@ -11,12 +11,18 @@
android:title="Device profile"> android:title="Device profile">
<ListPreference <ListPreference
android:key="pref_device_profile" android:key="pref_device_profile"
android:persistent="false"
android:title="Select profile" android:title="Select profile"
tools:summary="Automatic (Google Pixel 3, Android 11)" /> tools:summary="Automatic (Google Pixel 3, Android 11)" />
<Preference <Preference
android:key="pref_device_profile_import" android:key="pref_device_profile_import"
android:summary="Import device profile from file" 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>
<PreferenceCategory <PreferenceCategory
android:key="prefcat_device_registration_status" android:key="prefcat_device_registration_status"

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?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 ~ 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 from OPM3.171019.016, Mar 2018 -->
<data key="Build.BOARD" value="bullhead" /> <data key="Build.BOARD" value="bullhead" />
<data key="Build.BOOTLOADER" value="BHZ31b" /> <data key="Build.BOOTLOADER" value="BHZ31b" />
@ -27,6 +27,7 @@
<data key="Build.VERSION.CODENAME" value="REL" /> <data key="Build.VERSION.CODENAME" value="REL" />
<data key="Build.VERSION.INCREMENTAL" value="6d95f5a143" /> <data key="Build.VERSION.INCREMENTAL" value="6d95f5a143" />
<data key="Build.VERSION.RELEASE" value="8.1.0" /> <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" value="27" />
<data key="Build.VERSION.SDK_INT" value="27" /> <data key="Build.VERSION.SDK_INT" value="27" />
<data key="Build.SUPPORTED_ABIS" value="arm64-v8a,armeabi-v7a,armeabi" /> <data key="Build.SUPPORTED_ABIS" value="arm64-v8a,armeabi-v7a,armeabi" />

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2022 microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<profile name="Motorola Moto G (LineageOS 14.1)" product="lineage_falcon" sdk="25" id="lineage_falcon_25" auto="true">
<!-- Data from LineageOS 14.1-20190207-NIGHTLY-falcon, Feb 2019 -->
<data key="Build.BOARD" value="MSM8226" />
<data key="Build.BOOTLOADER" value="0x4118" />
<data key="Build.BRAND" value="motorola" />
<data key="Build.CPU_ABI" value="armeabi-v7a" />
<data key="Build.CPU_ABI2" value="armeabi" />
<data key="Build.DEVICE" value="falcon_umts" />
<data key="Build.DISPLAY" value="lineage_falcon-userdebug 7.1.2 NJH47F f4535aec29" />
<data key="Build.FINGERPRINT" value="motorola/falcon_retuglb/falcon_umts:5.1/LPB23.13-58/58:user/release-keys" />
<data key="Build.HARDWARE" value="qcom" />
<data key="Build.HOST" value="lineage-runner" />
<data key="Build.ID" value="NJH47F" />
<data key="Build.MANUFACTURER" value="motorola" />
<data key="Build.MODEL" value="Moto G" />
<data key="Build.PRODUCT" value="lineage_falcon" />
<data key="Build.RADIO" value="unknown" />
<data key="Build.TAGS" value="release-keys" />
<data key="Build.TIME" value="1549548437000" />
<data key="Build.TYPE" value="user" />
<data key="Build.USER" value="gitlab-runner" />
<data key="Build.VERSION.CODENAME" value="REL" />
<data key="Build.VERSION.INCREMENTAL" value="f4535aec29" />
<data key="Build.VERSION.RELEASE" value="7.1.2" />
<data key="Build.VERSION.SECURITY_PATCH" value="2019-01-05" />
<data key="Build.VERSION.SDK" value="25" />
<data key="Build.VERSION.SDK_INT" value="25" />
<data key="Build.SUPPORTED_ABIS" value="armeabi-v7a,armeabi" />
<serial template="TA9290XXXX" />
</profile>