diff --git a/play-services-basement/src/main/java/org/microg/gms/common/Constants.java b/play-services-basement/src/main/java/org/microg/gms/common/Constants.java
index c6ea9e002d..5cd9a55210 100644
--- a/play-services-basement/src/main/java/org/microg/gms/common/Constants.java
+++ b/play-services-basement/src/main/java/org/microg/gms/common/Constants.java
@@ -28,4 +28,5 @@ public class Constants {
public static final String MICROG_PACKAGE_SIGNATURE_SHA1 = "10321bd893f69af97f7573aafe9de1dc0901f3a1";
@Deprecated
public static final int MAX_REFERENCE_VERSION = GMS_VERSION_CODE;
+ public static final String GP_PACKAGE_NAME = "com.android.vending";
}
diff --git a/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java b/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java
index dd9a1be6ac..9ce0bbffdd 100644
--- a/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java
+++ b/play-services-core/src/main/java/org/microg/gms/auth/login/LoginActivity.java
@@ -22,6 +22,7 @@
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
+import android.content.Intent;
import android.graphics.Color;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
@@ -77,6 +78,7 @@
import static org.microg.gms.auth.AuthPrefs.isAuthVisible;
import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME;
import static org.microg.gms.common.Constants.GMS_VERSION_CODE;
+import static org.microg.gms.common.Constants.GP_PACKAGE_NAME;
public class LoginActivity extends AssistantActivity {
public static final String TMPL_NEW_ACCOUNT = "new_account";
@@ -91,6 +93,7 @@ public class LoginActivity extends AssistantActivity {
private static final String GOOGLE_SUITE_URL = "https://accounts.google.com/signin/continue";
private static final String MAGIC_USER_AGENT = " MinuteMaid";
private static final String COOKIE_OAUTH_TOKEN = "oauth_token";
+ private static final String ACTION_UPDATE_ACCOUNT = "com.google.android.gms.auth.GOOGLE_ACCOUNT_CHANGE";
private final FidoHandler fidoHandler = new FidoHandler(this);
private final DroidGuardHandler dgHandler = new DroidGuardHandler(this);
@@ -357,6 +360,10 @@ private void returnSuccessResponse(Account account){
bd.putString(AccountManager.KEY_ACCOUNT_TYPE,accountType);
response.onResult(bd);
}
+ Intent intent = new Intent(ACTION_UPDATE_ACCOUNT);
+ intent.setPackage(GP_PACKAGE_NAME);
+ intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
+ sendBroadcast(intent);
}
private void retrieveGmsToken(final Account account) {
final AuthManager authManager = new AuthManager(this, account.name, GMS_PACKAGE_NAME, "ac2dm");
diff --git a/vending-app/src/main/AndroidManifest.xml b/vending-app/src/main/AndroidManifest.xml
index eb12ae7378..c81e72f183 100644
--- a/vending-app/src/main/AndroidManifest.xml
+++ b/vending-app/src/main/AndroidManifest.xml
@@ -175,5 +175,13 @@
+
+
+
+
+
+
diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/DeviceSyncInfo.kt b/vending-app/src/main/kotlin/com/google/android/finsky/DeviceSyncInfo.kt
new file mode 100644
index 0000000000..3b9759e216
--- /dev/null
+++ b/vending-app/src/main/kotlin/com/google/android/finsky/DeviceSyncInfo.kt
@@ -0,0 +1,611 @@
+/**
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.finsky
+
+import android.accounts.Account
+import android.accounts.AccountManager
+import android.annotation.SuppressLint
+import android.app.ActivityManager
+import android.app.admin.DevicePolicyManager
+import android.content.Context
+import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
+import android.content.res.Configuration
+import android.graphics.Point
+import android.opengl.GLES10
+import android.os.Build
+import android.telephony.TelephonyManager
+import android.text.TextUtils
+import android.util.Base64
+import android.util.DisplayMetrics
+import android.util.Log
+import android.view.WindowManager
+import java.security.MessageDigest
+import java.security.NoSuchAlgorithmException
+import java.util.Arrays
+import java.util.Objects
+import java.util.Random
+import java.util.TimeZone
+import java.util.regex.Pattern
+import java.util.stream.Collectors
+import javax.microedition.khronos.egl.EGL10
+import javax.microedition.khronos.egl.EGLConfig
+import javax.microedition.khronos.egl.EGLContext
+import javax.microedition.khronos.egl.EGLDisplay
+import javax.microedition.khronos.egl.EGLSurface
+import kotlin.math.abs
+
+object DeviceSyncInfo {
+
+ private const val TAG = "DeviceSyncInfo"
+ private val glInfoList = ArrayList()
+
+ fun buildSyncRequest(context: Context, androidId: String, account: Account): SyncReqWrapper {
+ Log.d(TAG, "cachePayload: ")
+ val builder = SyncReqWrapper.Builder()
+ val payloads = buildPayloads(context, androidId, account)
+ val syncRequests = builder.request.toMutableList()
+ for (payload in payloads) {
+ payload?.run { syncRequests.add(this) }
+ }
+ builder.request = syncRequests
+ return builder.build()
+ }
+
+ private fun buildPayloads(context: Context, androidId: String, account: Account): Array {
+ val fetchedGlStrings: ArrayList = fetchGLInfo()
+ //---------------------------------------GPU info--------------------------------------------------------------------
+ val accountSha256 = accountSha256(androidId, account)
+ val accountAssValue = AccountAssValue.Builder().value_(accountSha256).build()
+ val accountAssociationPayload = AccountAssociationPayload.Builder().accountAss(accountAssValue).build()
+ val accountAssociationPayloadRequest = SyncRequest.Builder().accountAssociationPayload(accountAssociationPayload).build()
+ //--------------------------------------------------------------------------------------------------------------------
+ val carrierPropertiesPayloadRequest = createCarrierPropertiesPayloadRequest(context)
+ val deviceAccountsPayloadRequest = createDeviceAccountsPayloadRequest(context, androidId)
+ val deviceInfoCollect = createDeviceInfoCollect(context, fetchedGlStrings.toList())
+ val deviceCapabilitiesPayloadRequest = createDeviceCapabilitiesPayloadRequest(deviceInfoCollect)
+ val deviceInputPropertiesPayloadRequest = createDeviceInputPropertiesPayloadRequest(deviceInfoCollect)
+ val deviceModelPayloadRequest = createDeviceModelPayloadRequest()
+ val enterprisePropertiesPayloadRequest = createEnterprisePropertiesPayloadRequest(context)
+ val hardwareIdentifierPayloadRequest = createHardwareIdentifierPayloadRequest(context)
+ val hardwarePropertiesPayloadRequest = createHardwarePropertiesPayloadRequest(deviceInfoCollect)
+ val localePropertiesPayloadRequest = createLocalePropertiesPayloadRequest()
+ val playPartnerPropertiesPayloadRequest = createPlayPartnerPropertiesPayloadRequest()
+ val playPropertiesPayloadRequest = createPlayPropertiesPayload(context)
+ val screenPropertiesPayloadRequest = createScreenPropertiesPayloadRequest(deviceInfoCollect)
+ val systemPropertiesPayloadRequest = createSystemPropertiesPayloadRequest(deviceInfoCollect)
+ val gpuPayloadRequest = createGpuPayloadRequest(fetchedGlStrings.toList())
+ return arrayOf(
+ accountAssociationPayloadRequest, carrierPropertiesPayloadRequest, deviceAccountsPayloadRequest,
+ deviceCapabilitiesPayloadRequest, deviceInputPropertiesPayloadRequest, deviceModelPayloadRequest,
+ enterprisePropertiesPayloadRequest, hardwareIdentifierPayloadRequest, hardwarePropertiesPayloadRequest,
+ localePropertiesPayloadRequest, playPartnerPropertiesPayloadRequest, playPropertiesPayloadRequest,
+ screenPropertiesPayloadRequest, systemPropertiesPayloadRequest, gpuPayloadRequest
+ )
+ }
+
+ private fun createDeviceInfoCollect(context: Context, gpuInfoList: List): DeviceInfoCollect {
+ val builder = DeviceInfoCollect.Builder()
+ .reqTouchScreen(0)
+ .reqKeyboardType(0)
+ .reqNavigation(0)
+ .deviceStablePoint(0)
+ .reqInputFeaturesV1(false)
+ .reqInputFeaturesV2(false)
+ .deviceStable(0)
+ .reqGlEsVersion(0)
+ val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
+ val configurationInfo = activityManager.deviceConfigurationInfo
+ if (configurationInfo != null) {
+ if (configurationInfo.reqTouchScreen != Configuration.TOUCHSCREEN_UNDEFINED) {
+ builder.reqTouchScreen(configurationInfo.reqTouchScreen)
+ }
+ if (configurationInfo.reqKeyboardType != Configuration.KEYBOARD_UNDEFINED) {
+ builder.reqKeyboardType(configurationInfo.reqKeyboardType)
+ }
+ if (configurationInfo.reqNavigation != Configuration.NAVIGATION_UNDEFINED) {
+ builder.reqNavigation(configurationInfo.reqNavigation)
+ }
+ builder.reqGlEsVersion(configurationInfo.reqGlEsVersion)
+ builder.reqInputFeaturesV1((configurationInfo.reqInputFeatures and 1) == 1)
+ .reqInputFeaturesV2((configurationInfo.reqInputFeatures and 2) > 0)
+ }
+ val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
+ val size = Point()
+ windowManager.defaultDisplay.getSize(size)
+ builder.displayX(size.x).displayY(size.y)
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ builder.deviceStable(DisplayMetrics.DENSITY_DEVICE_STABLE)
+ .deviceStablePoint(calculatePoint(size, DisplayMetrics.DENSITY_DEVICE_STABLE))
+ }
+ val configuration = context.resources.configuration
+ builder.screenLayout(configuration.screenLayout)
+ .smallestScreenWidthDp(configuration.smallestScreenWidthDp)
+ .systemSharedLibraryNames(listOf(*Objects.requireNonNull(context.packageManager.systemSharedLibraryNames)))
+ .locales(listOf(*context.assets.locales))
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ builder.glExtensions(gpuInfoList.stream()
+ .flatMap { fetchedGlStrings: FetchedGlStrings -> fetchedGlStrings.glExtensions?.let { Arrays.stream(it.toTypedArray()) } }
+ .collect(Collectors.toList()))
+ .isLowRamDevice(activityManager.isLowRamDevice)
+ }
+ val memoryInfo = ActivityManager.MemoryInfo()
+ activityManager.getMemoryInfo(memoryInfo)
+ builder.totalMem(memoryInfo.totalMem)
+ .availableProcessors(Runtime.getRuntime().availableProcessors())
+ val systemAvailableFeatures = context.packageManager.systemAvailableFeatures
+ for (featureInfo in systemAvailableFeatures) {
+ if (!TextUtils.isEmpty(featureInfo.name)) {
+ var featureInfoProto = FeatureInfoProto.Builder().build()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ featureInfoProto = FeatureInfoProto.Builder().name(featureInfo.name).version(featureInfo.version).build()
+ }
+ builder.featureInfoList = builder.featureInfoList.toMutableList().apply {
+ add(featureInfoProto)
+ }
+ builder.featureNames = builder.featureNames.toMutableList().apply {
+ add(featureInfoProto.name!!)
+ }
+ }
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ builder.supportedAbi(listOf(*Build.SUPPORTED_ABIS))
+ }
+ var prop = getSystemProperty("ro.oem.key1", "")
+ if (!TextUtils.isEmpty(prop)) {
+ builder.oemKey(prop)
+ }
+ builder.buildCodeName(Build.VERSION.CODENAME)
+ prop = getSystemProperty("ro.build.version.preview_sdk_fingerprint", "")
+ if (!TextUtils.isEmpty(prop)) {
+ builder.previewSdkFingerprint(prop)
+ }
+ return builder.build()
+ }
+
+ private fun createGpuPayloadRequest(glStringsList: List): SyncRequest? {
+ var gpuPayloadRequest: SyncRequest? = null
+ try {
+ var infos = glStringsList
+ var gpuPayloads = emptyList()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ infos = infos.stream()
+ .filter { fetchedGlStrings: FetchedGlStrings ->
+ fetchedGlStrings.glRenderer!!.isNotEmpty() || fetchedGlStrings.glVendor!!.isNotEmpty() || fetchedGlStrings.glVersion!!.isNotEmpty()
+ }.collect(Collectors.toList())
+ val maxVersion = infos.stream()
+ .max(Comparator.comparingInt { fetchedGlStrings: FetchedGlStrings ->
+ fetchedGlStrings.contextClientVersion
+ }).map { obj: FetchedGlStrings ->
+ obj.contextClientVersion
+ }
+ if (maxVersion.isPresent) {
+ infos = infos.stream()
+ .filter { fetchedGlStrings: FetchedGlStrings ->
+ fetchedGlStrings.contextClientVersion == maxVersion.get()
+ }.collect(Collectors.toList())
+ }
+ gpuPayloads = infos.stream().map { fetchedGlStrings: FetchedGlStrings ->
+ val gpuInfoWrapper = GpuInfoWrapper.Builder()
+ if (!TextUtils.isEmpty(fetchedGlStrings.glRenderer)) gpuInfoWrapper.glRenderer(fetchedGlStrings.glRenderer)
+ if (!TextUtils.isEmpty(fetchedGlStrings.glVendor)) gpuInfoWrapper.glVendor(fetchedGlStrings.glVendor)
+ if (!TextUtils.isEmpty(fetchedGlStrings.glVersion)) gpuInfoWrapper.glVersion(fetchedGlStrings.glVersion)
+ GpuPayload.Builder().gpuInfo(gpuInfoWrapper.build()).build()
+ }.distinct().collect(Collectors.toList())
+ }
+ gpuPayloadRequest = SyncRequest.Builder().gpuPayload(if (gpuPayloads.isEmpty()) GpuPayload.Builder().build() else gpuPayloads[0]).build()
+ } catch (e: Exception) {
+ Log.w(TAG, "createGpuPayloadRequest error", e)
+ }
+ return gpuPayloadRequest
+ }
+
+ private fun createHardwarePropertiesPayloadRequest(deviceInfoCollect: DeviceInfoCollect): SyncRequest {
+ val hardwarePropertiesPayload = HardwarePropertiesPayload.Builder()
+ .isLowRamDevice(deviceInfoCollect.isLowRamDevice)
+ .totalMem(deviceInfoCollect.totalMem)
+ .availableProcessors(deviceInfoCollect.availableProcessors)
+ .supportedAbi(deviceInfoCollect.supportedAbi)
+ .build()
+ return SyncRequest.Builder().hardwarePropertiesPayload(hardwarePropertiesPayload).build()
+ }
+
+ @SuppressLint("DefaultLocale")
+ private fun createLocalePropertiesPayloadRequest(): SyncRequest {
+ val timeZone = TimeZone.getDefault()
+ val gmtFormat = String.format(
+ "GMT%+d:%02d",
+ timeZone.rawOffset / (60 * 60 * 1000),
+ abs(timeZone.rawOffset / (60 * 1000) % 60)
+ )
+ val localePropertiesPayload = LocalePropertiesPayload.Builder()
+ .locale(gmtFormat)
+ .build()
+ return SyncRequest.Builder().localePropertiesPayload(localePropertiesPayload).build()
+ }
+
+ private fun createPlayPartnerPropertiesPayloadRequest(): SyncRequest {
+ val playPartnerPropertiesPayload = PlayPartnerPropertiesPayload.Builder()
+ .marketId("am-google")
+ .partnerIdMs("play-ms-android-google")
+ .partnerIdAd("play-ad-ms-android-google")
+ .build()
+ return SyncRequest.Builder().playPartnerPropertiesPayload(playPartnerPropertiesPayload).build()
+ }
+
+ private fun createPlayPropertiesPayload(context: Context): SyncRequest {
+ var version = 0
+ try {
+ version = context.packageManager.getPackageInfo("com.android.vending", 0).versionCode
+ } catch (exception: PackageManager.NameNotFoundException) {
+ Log.w(TAG, "[DAS] Could not find our package", exception)
+ }
+ val playPropertiesPayload = PlayPropertiesPayload.Builder().playVersion(version).build()
+ return SyncRequest.Builder().playPropertiesPayload(playPropertiesPayload).build()
+ }
+
+ private fun createScreenPropertiesPayloadRequest(deviceInfoCollect: DeviceInfoCollect): SyncRequest {
+ val screenPropertiesPayload = ScreenPropertiesPayload.Builder()
+ .reqTouchScreen(deviceInfoCollect.reqTouchScreen)
+ .displayX(deviceInfoCollect.displayX)
+ .displayY(deviceInfoCollect.displayY)
+ .deviceStablePoint(deviceInfoCollect.deviceStablePoint)
+ .deviceStable(deviceInfoCollect.deviceStable)
+ .build()
+ return SyncRequest.Builder().screenPropertiesPayload(screenPropertiesPayload).build()
+ }
+
+ private fun createSystemPropertiesPayloadRequest(deviceInfoCollect: DeviceInfoCollect): SyncRequest {
+ val systemPropertiesPayload = SystemPropertiesPayload.Builder()
+ .fingerprint(Build.FINGERPRINT)
+ .sdkInt(Build.VERSION.SDK_INT.toLong())
+ .previewSdkFingerprint(deviceInfoCollect.previewSdkFingerprint)
+ .buildCodeName(deviceInfoCollect.buildCodeName)
+ .oemKey(deviceInfoCollect.oemKey)
+ .reqGlEsVersion(deviceInfoCollect.reqGlEsVersion)
+ .build()
+ return SyncRequest.Builder().systemPropertiesPayload(systemPropertiesPayload).build()
+ }
+
+ private fun createHardwareIdentifierPayloadRequest(context: Context): SyncRequest? {
+ var hardwareIdentifierPayloadRequest: SyncRequest? = null
+ try {
+ val builder = HardwareIdentifierPayload.Builder()
+ val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
+ val randomIMEI = generateRandomIMEI()
+ var imeid: Long = if (TextUtils.isEmpty(randomIMEI) || !Pattern.compile("^[0-9]{15}$").matcher(randomIMEI)
+ .matches()
+ ) 0L else randomIMEI.toLong(10) or 0x1000000000000000L
+ if (imeid == 0L) {
+ var meid = ""
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ meid = telephonyManager.meid
+ }
+ if (!TextUtils.isEmpty(meid) && Pattern.compile("^[0-9a-fA-F]{14}$").matcher(meid).matches()) {
+ imeid = meid.toLong(16) or 0x1100000000000000L
+ if (imeid == 0L) {
+ if (context.packageManager.checkPermission(
+ "android.permission.READ_PRIVILEGED_PHONE_STATE",
+ "com.android.vending"
+ ) == PackageManager.PERMISSION_GRANTED
+ ) {
+ var serial = ""
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ serial = Build.getSerial()
+ }
+ if (TextUtils.isEmpty(serial) && serial != "unknown") {
+ try {
+ val serialShaByte = MessageDigest.getInstance("SHA1").digest(serial.toByteArray())
+ imeid =
+ ((serialShaByte[0].toLong()) and 0xFFL) shl 0x30 or 0x1400000000000000L or (((serialShaByte[1].toLong()) and 0xFFL) shl 40) or (((serialShaByte[2].toLong()) and 0xFFL) shl 0x20) or (((serialShaByte[3].toLong()) and 0xFFL) shl 24) or (((serialShaByte[4].toLong()) and 0xFFL) shl 16) or (((serialShaByte[5].toLong()) and 0xFFL) shl 8) or ((serialShaByte[6].toLong()) and 0xFFL)
+ } catch (noSuchAlgorithmException0: NoSuchAlgorithmException) {
+ Log.w(TAG, "No support for sha1?")
+ }
+ }
+ }
+ }
+ }
+ }
+ builder.imeId(imeid)
+ hardwareIdentifierPayloadRequest = SyncRequest.Builder().hardwareIdentifierPayload(builder.build()).build()
+ } catch (e: Exception) {
+ Log.w(TAG, "createHardwareIdentifierPayloadRequest error", e)
+ }
+ return hardwareIdentifierPayloadRequest
+ }
+
+ private fun createEnterprisePropertiesPayloadRequest(context: Context): SyncRequest? {
+ var enterprisePropertiesPayloadRequest: SyncRequest? = null
+ try {
+ val enterprisePropertiesPayload = EnterprisePropertiesPayload.Builder()
+ val devicePolicyManager = context.getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
+ val activeAdmins = devicePolicyManager.activeAdmins
+ if (activeAdmins != null) {
+ for (componentName in activeAdmins) {
+ val packageName = componentName.packageName
+ val packageInfo: PackageInfo? = context.packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES)
+ val isDeviceOwner = devicePolicyManager.isDeviceOwnerApp(packageName)
+ var isProfileOwner = false
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ isProfileOwner = devicePolicyManager.isProfileOwnerApp(packageName)
+ }
+ val policyType =
+ if (isDeviceOwner) MangedScope.MANAGED_DEVICES else if (isProfileOwner) MangedScope.MANAGED_PROFILES else MangedScope.LEGACY_DEVICE_ADMINS
+ val profileInfo = ProfileInfo.Builder()
+ .pkgName(componentName.packageName)
+ .policyType(policyType)
+ .pkgSHA1(calculateSHA(packageInfo!!.signatures[0].toByteArray(), "SHA1"))
+ .pkgSHA256(calculateSHA(packageInfo.signatures[0].toByteArray(), "SHA256")).build()
+ if (isProfileOwner) {
+ enterprisePropertiesPayload.profileOwner(profileInfo)
+ }
+ enterprisePropertiesPayload.default = enterprisePropertiesPayload.default.toMutableList()
+ .apply { add(profileInfo) }
+ }
+ }
+ enterprisePropertiesPayloadRequest = SyncRequest.Builder().enterprisePropertiesPayload(enterprisePropertiesPayload.build()).build()
+ } catch (e: Exception) {
+ Log.w(TAG, "createEnterprisePropertiesPayloadRequest error", e)
+ }
+ return enterprisePropertiesPayloadRequest
+ }
+
+ private fun createDeviceInputPropertiesPayloadRequest(deviceInfoCollect: DeviceInfoCollect): SyncRequest {
+ val builder = DeviceInputPropertiesPayload.Builder()
+ .reqInputFeatures(deviceInfoCollect.reqInputFeaturesV1)
+ .reqKeyboardType(deviceInfoCollect.reqKeyboardType)
+ .reqNavigation(deviceInfoCollect.reqNavigation)
+ return SyncRequest.Builder().deviceInputPropertiesPayload(builder.build()).build()
+ }
+
+ private fun createDeviceModelPayloadRequest(): SyncRequest {
+ val builder = DeviceModelPayload.Builder()
+ .manufacturer(Build.MANUFACTURER)
+ .model(Build.MODEL)
+ .device(Build.DEVICE)
+ .product(Build.PRODUCT)
+ .brand(Build.BRAND)
+ return SyncRequest.Builder().deviceModelPayload(builder.build()).build()
+ }
+
+ private fun createDeviceCapabilitiesPayloadRequest(deviceInfoCollect: DeviceInfoCollect): SyncRequest {
+ val builder = DeviceCapabilitiesPayload.Builder()
+ builder.glExtensions(deviceInfoCollect.glExtensions)
+ val featureInfoList = builder.featureInfo.toMutableList()
+ for (featureInfoProto in deviceInfoCollect.featureInfoList) {
+ featureInfoList.add(
+ FeatureInfoProto.Builder()
+ .name(featureInfoProto.name)
+ .version(featureInfoProto.version)
+ .build()
+ )
+ }
+ builder.featureInfo = featureInfoList
+ builder.systemSharedLibraryNames(deviceInfoCollect.systemSharedLibraryNames)
+ .locales(deviceInfoCollect.locales)
+ .unknownFlag(false)
+ return SyncRequest.Builder().deviceCapabilitiesPayload(builder.build()).build()
+ }
+
+ private fun createDeviceAccountsPayloadRequest(context: Context, androidId: String): SyncRequest? {
+ var deviceAccountsPayloadRequest: SyncRequest? = null
+ try {
+ val accountManager = context.getSystemService(Context.ACCOUNT_SERVICE) as AccountManager
+ val accounts = accountManager.accounts
+ val builder = DeviceAccountsPayload.Builder()
+ val accountAssValues = builder.accountAss.toMutableList()
+ for (account in accounts) {
+ accountAssValues.add(AccountAssValue.Builder().value_(accountSha256(androidId, account)).build())
+ }
+ builder.accountAss = accountAssValues
+ deviceAccountsPayloadRequest = SyncRequest.Builder().deviceAccountsPayload(builder.build()).build()
+ } catch (e: Exception) {
+ Log.w(TAG, "createDeviceAccountsPayloadRequest error", e)
+ }
+ return deviceAccountsPayloadRequest
+ }
+
+ @SuppressLint("HardwareIds")
+ private fun createCarrierPropertiesPayloadRequest(context: Context): SyncRequest? {
+ var carrierPropertiesPayloadRequest: SyncRequest? = null
+ try {
+ val telephonyManager = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
+ var simCardId = 0
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ simCardId = telephonyManager.simCarrierId
+ }
+ var carrierIdFromSimMccMnc = 0
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ carrierIdFromSimMccMnc = telephonyManager.carrierIdFromSimMccMnc
+ }
+ val telephonyInfo = TelephonyInfo.Builder().subscriberId(((telephonyManager.subscriberId.toLong() / 100000L).toString() + "00000").toLong())
+ .operatorName(telephonyManager.simOperatorName).groupIdLevel(telephonyManager.groupIdLevel1).simCardId(simCardId)
+ .carrierIdFromSimMccMnc(carrierIdFromSimMccMnc).build()
+ val telephonyStateWrapper = TelephonyStateWrapper.Builder().telephonyInfo(telephonyInfo).build()
+ val carrierPropertiesPayload =
+ CarrierPropertiesPayload.Builder().telephonyStateValue(telephonyStateWrapper).simOperator(telephonyManager.simOperator).build()
+ carrierPropertiesPayloadRequest = SyncRequest.Builder().carrierPropertiesPayload(carrierPropertiesPayload).build()
+ } catch (securityException: SecurityException) {
+ Log.w(TAG, "SecurityException when reading IMSI.", securityException)
+ } catch (stateException: IllegalStateException) {
+ Log.w(TAG, "IllegalStateException when reading IMSI. This is a known SDK 31 Samsung bug.", stateException)
+ } catch (e: Exception) {
+ Log.w(TAG, "createCarrierPropertiesPayloadRequest error", e)
+ }
+ return carrierPropertiesPayloadRequest
+ }
+
+ private fun accountSha256(androidId: String, account: Account): String? {
+ return try {
+ val androidIdAcc = (androidId + "-" + account.name).toByteArray()
+ val messageDigest0 = MessageDigest.getInstance("SHA256")
+ messageDigest0.update(androidIdAcc, 0, androidIdAcc.size)
+ Base64.encodeToString(messageDigest0.digest(), 11)
+ } catch (ignored: Exception) {
+ null
+ }
+ }
+
+ private fun generateRandomIMEI(): String {
+ val random = Random()
+ val imeiBuilder = StringBuilder()
+ for (i in 0..13) {
+ val digit = random.nextInt(10)
+ imeiBuilder.append(digit)
+ }
+ val imei = imeiBuilder.toString()
+ val checkDigit = calculateCheckDigit(imei)
+ imeiBuilder.append(checkDigit)
+ return imeiBuilder.toString()
+ }
+
+ private fun calculateCheckDigit(imei: String): Int {
+ var sum = 0
+ for (i in imei.indices) {
+ var digit = Character.getNumericValue(imei[i])
+ if (i % 2 == 1) {
+ digit *= 2
+ }
+ if (digit > 9) {
+ digit -= 9
+ }
+ sum += digit
+ }
+ return (10 - (sum % 10)) % 10
+ }
+
+ private fun calculateSHA(data: ByteArray, algorithm: String?): String? {
+ val messageDigest0: MessageDigest
+ try {
+ messageDigest0 = algorithm?.let { MessageDigest.getInstance(it) }!!
+ } catch (noSuchAlgorithmException0: NoSuchAlgorithmException) {
+ Log.w(TAG, "[DC] No support for %s?", noSuchAlgorithmException0)
+ return null
+ }
+ messageDigest0.update(data, 0, data.size)
+ return Base64.encodeToString(messageDigest0.digest(), 11)
+ }
+
+ private fun fetchGLInfo(): ArrayList {
+ if (glInfoList.isNotEmpty()) return glInfoList
+ try {
+ val eGL100 = EGLContext.getEGL() as? EGL10
+ val result = ArrayList()
+ val egl10Instance = eGL100?.let { EGL10Wrapper(it) }
+ val eglDisplay = eGL100!!.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY)
+ eGL100.eglInitialize(eglDisplay, IntArray(2))
+ val ints = IntArray(1)
+ val configCount = if (eGL100.eglGetConfigs(eglDisplay, null, 0, ints)) ints[0] else 0
+ val arrEglConfig = arrayOfNulls(configCount)
+ val eglConfigs = if (eGL100.eglGetConfigs(eglDisplay, arrEglConfig, configCount, IntArray(1))) arrEglConfig else null
+ val arrV1 = intArrayOf(0x3057, 1, 0x3056, 1, 0x3038)
+ for (v1 in 0 until configCount) {
+ if (egl10Instance?.eglGetConfigAttrib(eglDisplay, eglConfigs?.get(v1), 0x3027) != 0x3050
+ && (egl10Instance?.eglGetConfigAttrib(eglDisplay, eglConfigs?.get(v1), 0x3033)?.and(1)) != 0
+ ) {
+ val v2 = egl10Instance?.eglGetConfigAttrib(eglDisplay, eglConfigs?.get(v1), 0x3040)
+ if ((v2?.and(1)) != 0) {
+ egl10Instance?.let { wrapper -> buildGLStrings(wrapper, eglDisplay, eglConfigs?.get(v1), arrV1, null)?.let { result.add(it) } }
+ }
+ if ((v2?.and(4)) != 0) {
+ egl10Instance?.let { wrapper ->
+ buildGLStrings(
+ wrapper,
+ eglDisplay,
+ eglConfigs?.get(v1),
+ arrV1,
+ intArrayOf(0x3098, 2, 0x3038)
+ )?.let { result.add(it) }
+ }
+ }
+ }
+ }
+ egl10Instance?.instance?.eglTerminate(eglDisplay)
+ return result.also { glInfoList.addAll(it) }
+ } catch (e: Exception) {
+ Log.d(TAG, "fetchGLInfo: error", e)
+ }
+ return ArrayList()
+ }
+
+ private fun buildGLStrings(egl10Tools: EGL10Wrapper, eglDisplay: EGLDisplay, eglConfig: EGLConfig?, arrV: IntArray, arrV1: IntArray?): FetchedGlStrings? {
+ val eglContext = egl10Tools.instance.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, arrV1)
+ if (eglContext != EGL10.EGL_NO_CONTEXT) {
+ val eglSurface = egl10Tools.instance.eglCreatePbufferSurface(eglDisplay, eglConfig, arrV)
+ if (eglSurface == EGL10.EGL_NO_SURFACE) {
+ egl10Tools.eglDestroyContext(eglDisplay, eglContext)
+ return null
+ }
+ egl10Tools.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)
+ val result = FetchedGlStrings(0, null, null, null, null)
+ val glExtensions = GLES10.glGetString(GLES10.GL_EXTENSIONS)
+ if (!TextUtils.isEmpty(glExtensions)) {
+ result.glExtensions = glExtensions.split(" ".toRegex()).dropLastWhile { it.isEmpty() }
+ }
+ result.glRenderer = GLES10.glGetString(GLES10.GL_RENDERER)
+ result.glVendor = GLES10.glGetString(GLES10.GL_VENDOR)
+ result.glVersion = GLES10.glGetString(GLES10.GL_VERSION)
+ if (result.glExtensions != null) {
+ egl10Tools.eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT)
+ egl10Tools.instance.eglDestroySurface(eglDisplay, eglSurface)
+ egl10Tools.eglDestroyContext(eglDisplay, eglContext)
+ return result
+ }
+ throw IllegalStateException("Missing required properties ")
+ }
+ return null
+ }
+
+ private fun getSystemProperty(key: String?, defaultValue: String?): String? {
+ var value = defaultValue
+ try {
+ @SuppressLint("PrivateApi") val systemPropertiesClass = Class.forName("android.os.SystemProperties")
+ val getMethod = systemPropertiesClass.getMethod("get", String::class.java, String::class.java)
+ value = getMethod.invoke(null, key, defaultValue) as String
+ } catch (e: Exception) {
+ Log.w(TAG, "Unable to retrieve system property", e)
+ }
+ return value
+ }
+
+ private fun calculatePoint(point: Point, v: Int): Int {
+ val f = point.x.toFloat()
+ val v1 = ((point.y.toFloat()) * (160.0f / (v.toFloat()))).toInt()
+ if (v1 < 470) {
+ return 17
+ }
+ val v2 = (f * (160.0f / (v.toFloat()))).toInt()
+ if (v1 >= 960 && v2 >= 720) {
+ return if (v1 * 3 / 5 < v2 - 1) 20 else 4
+ }
+ val v3 = if (v1 < 640 || v2 < 480) 2 else 3
+ return if (v1 * 3 / 5 < v2 - 1) v3 or 16 else v3
+ }
+
+ internal class EGL10Wrapper(val instance: EGL10) {
+ fun eglGetConfigAttrib(eglDisplay: EGLDisplay?, eglConfig: EGLConfig?, v: Int): Int {
+ val value = IntArray(1)
+ instance.eglGetConfigAttrib(eglDisplay, eglConfig, v, value)
+ return value[0]
+ }
+
+ fun eglDestroyContext(eglDisplay: EGLDisplay?, eglContext: EGLContext?) {
+ instance.eglDestroyContext(eglDisplay, eglContext)
+ }
+
+ fun eglMakeCurrent(eglDisplay: EGLDisplay?, draw: EGLSurface?, read: EGLSurface?, eglContext: EGLContext?) {
+ instance.eglMakeCurrent(eglDisplay, draw, read, eglContext)
+ }
+ }
+
+ internal class FetchedGlStrings(
+ var contextClientVersion: Int,
+ var glExtensions: List?,
+ var glRenderer: String?,
+ var glVendor: String?,
+ var glVersion: String?
+ )
+}
\ No newline at end of file
diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/accounts/impl/AccountsChangedReceiver.kt b/vending-app/src/main/kotlin/com/google/android/finsky/accounts/impl/AccountsChangedReceiver.kt
new file mode 100644
index 0000000000..529ac7af6f
--- /dev/null
+++ b/vending-app/src/main/kotlin/com/google/android/finsky/accounts/impl/AccountsChangedReceiver.kt
@@ -0,0 +1,62 @@
+/**
+ * SPDX-FileCopyrightText: 2024 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package com.google.android.finsky.accounts.impl
+
+import android.accounts.AccountManager
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import com.android.vending.licensing.AUTH_TOKEN_SCOPE
+import com.android.vending.licensing.getAuthToken
+import com.android.vending.licensing.getLicenseRequestHeaders
+import com.google.android.finsky.SyncResponse
+import com.google.android.finsky.DeviceSyncInfo
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import org.microg.gms.auth.AuthConstants
+import org.microg.gms.profile.ProfileManager
+import org.microg.vending.billing.GServices
+import org.microg.vending.billing.core.HttpClient
+import java.lang.RuntimeException
+
+private const val TAG = "AccountsChangedReceiver"
+
+class AccountsChangedReceiver : BroadcastReceiver() {
+
+ @OptIn(DelicateCoroutinesApi::class)
+ override fun onReceive(context: Context, intent: Intent?) {
+ Log.d(TAG, "onReceive: intent-> $intent")
+ var accountName: String? = null
+ if (intent?.let { accountName = it.getStringExtra(AccountManager.KEY_ACCOUNT_NAME) } == null) {
+ return
+ }
+ GlobalScope.launch(Dispatchers.IO) {
+ runCatching {
+ val account = AccountManager.get(context).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE).firstOrNull {
+ it.name == accountName
+ } ?: throw RuntimeException("account is null")
+ val oauthToken = account.let {
+ AccountManager.get(context).getAuthToken(it, AUTH_TOKEN_SCOPE, false).getString(AccountManager.KEY_AUTHTOKEN)
+ } ?: throw RuntimeException("oauthToken is null")
+ ProfileManager.ensureInitialized(context)
+ val androidId = GServices.getString(context.contentResolver, "android_id", "0")?.toLong() ?: 1
+ HttpClient(context).post(
+ url = "https://play-fe.googleapis.com/fdfe/sync",
+ headers = getLicenseRequestHeaders(oauthToken, androidId),
+ payload = DeviceSyncInfo.buildSyncRequest(context, androidId.toString(), account),
+ adapter = SyncResponse.ADAPTER
+ )
+ Log.d(TAG, "onReceive: sync success")
+ }.onFailure {
+ Log.d(TAG, "onReceive: sync error", it)
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/vending-app/src/main/proto/SyncRequest.proto b/vending-app/src/main/proto/SyncRequest.proto
new file mode 100644
index 0000000000..546b390380
--- /dev/null
+++ b/vending-app/src/main/proto/SyncRequest.proto
@@ -0,0 +1,188 @@
+option java_package = "com.google.android.finsky";
+option java_multiple_files = true;
+
+message SyncReqWrapper {
+ repeated SyncRequest request = 1;
+}
+
+message SyncRequest {
+ oneof payload {
+ AccountAssociationPayload accountAssociationPayload = 7;
+ DeviceAccountsPayload deviceAccountsPayload = 8;
+ CarrierPropertiesPayload carrierPropertiesPayload = 9;
+ DeviceCapabilitiesPayload deviceCapabilitiesPayload = 10;
+ DeviceInputPropertiesPayload deviceInputPropertiesPayload = 11;
+ DeviceModelPayload deviceModelPayload = 12;
+ EnterprisePropertiesPayload enterprisePropertiesPayload = 13;
+ HardwareIdentifierPayload hardwareIdentifierPayload = 14;
+ HardwarePropertiesPayload hardwarePropertiesPayload = 15;
+ LocalePropertiesPayload localePropertiesPayload = 16;
+ NotificationRoutingInfoPayload notificationRoutingInfoPayload = 17;
+ PlayPartnerPropertiesPayload playPartnerPropertiesPayload = 18;
+ PlayPropertiesPayload playPropertiesPayload = 19;
+ ScreenPropertiesPayload screenPropertiesPayload = 20;
+ SystemPropertiesPayload systemPropertiesPayload = 21;
+ GpuPayload gpuPayload = 24;
+ }
+}
+
+message AccountAssociationPayload {
+ optional AccountAssValue accountAss = 1;
+}
+
+message AccountAssValue {
+ optional string value = 1;
+}
+
+message DeviceAccountsPayload {
+ repeated AccountAssValue accountAss = 1;
+}
+
+message CarrierPropertiesPayload {
+ optional string simOperator = 1;
+ optional TelephonyStateWrapper telephonyStateValue = 2;
+}
+
+message TelephonyStateWrapper {
+ optional TelephonyInfo telephonyInfo = 1;
+}
+
+message TelephonyInfo {
+ optional int64 subscriberId = 1;
+ optional string operatorName = 2;
+ optional string groupIdLevel = 3;
+ optional int32 simCardId = 6;
+ optional int32 carrierIdFromSimMccMnc = 7;
+}
+
+message DeviceCapabilitiesPayload {
+ repeated FeatureInfoProto featureInfo = 1;
+ repeated string systemSharedLibraryNames = 2;
+ repeated string locales = 3;
+ repeated string glExtensions = 4;
+ optional bool unknownFlag = 5;
+}
+
+message DeviceInputPropertiesPayload {
+ optional int32 reqKeyboardType = 1;
+ optional bool reqInputFeatures = 2;
+ optional int32 reqNavigation = 3;
+}
+
+message DeviceModelPayload {
+ optional string manufacturer = 1;
+ optional string model = 2;
+ optional string device = 3;
+ optional string product = 4;
+ optional string brand = 5;
+}
+
+message EnterprisePropertiesPayload {
+ optional ProfileInfo profileOwner = 1;
+ repeated ProfileInfo default = 2;
+}
+
+message ProfileInfo {
+ optional string pkgName = 1;
+ optional string pkgSHA1 = 2;
+ optional string pkgSHA256 = 3;
+ optional MangedScope policyType = 4;
+}
+
+enum MangedScope {
+ UNKNOWN_MANAGED_SCOPE = 0;
+ MANAGED_DEVICES = 1;
+ MANAGED_PROFILES = 2;
+ MANAGED_AVENGER = 3;
+ LEGACY_DEVICE_ADMINS = 4;
+}
+
+message HardwareIdentifierPayload {
+ optional fixed64 imeId = 1;
+}
+
+message HardwarePropertiesPayload {
+ optional bool isLowRamDevice = 1;
+ optional int64 totalMem = 2;
+ optional int32 availableProcessors = 3;
+ repeated string supportedAbi = 4;
+}
+
+message LocalePropertiesPayload {
+ optional string locale = 1;
+}
+
+message NotificationRoutingInfoPayload {
+ optional string info = 1;
+}
+
+message PlayPartnerPropertiesPayload {
+ optional string marketId = 1;
+ optional string partnerIdMs = 2;
+ optional string partnerIdAd = 3;
+}
+
+message PlayPropertiesPayload {
+ optional int32 playVersion = 2;
+}
+
+message ScreenPropertiesPayload {
+ optional int32 reqTouchScreen = 1;
+ optional int32 displayX = 2;
+ optional int32 displayY = 3;
+ optional int32 deviceStablePoint = 4;
+ optional int32 deviceStable = 5;
+}
+
+message SystemPropertiesPayload {
+ optional string fingerprint = 1;
+ optional int64 sdkInt = 2;
+ optional string previewSdkFingerprint = 3;
+ optional string buildCodeName = 4;
+ optional string oemKey = 5;
+ optional int32 reqGlEsVersion = 6;
+}
+
+message GpuPayload {
+ optional GpuInfoWrapper gpuInfo = 1;
+}
+
+message GpuInfoWrapper {
+ optional string glRenderer = 1;
+ optional string glVendor = 2;
+ optional string glVersion = 3;
+}
+
+message DeviceInfoCollect {
+ optional int32 reqTouchScreen = 1;
+ optional int32 reqKeyboardType = 2;
+ optional int32 reqNavigation = 3;
+ optional int32 deviceStablePoint = 4;
+ optional bool reqInputFeaturesV1 = 5;
+ optional bool reqInputFeaturesV2 = 6;
+ optional int32 deviceStable = 7;
+ optional int32 reqGlEsVersion = 8;
+ repeated string systemSharedLibraryNames = 9;
+ repeated string featureNames = 10;
+ repeated string supportedAbi = 11;
+ optional int32 displayX = 12;
+ optional int32 displayY = 13;
+ repeated string locales = 14;
+ repeated string glExtensions = 15;
+ optional int32 smallestScreenWidthDp = 18;
+ optional bool isLowRamDevice = 19;
+ optional int64 totalMem = 20;
+ optional int32 availableProcessors = 21;
+ repeated FeatureInfoProto featureInfoList = 26;
+ optional int32 screenLayout = 27;
+ optional string oemKey = 29;
+ optional string buildCodeName = 30;
+ optional string previewSdkFingerprint = 31;
+}
+
+message FeatureInfoProto {
+ optional string name = 1;
+ optional int32 version = 2;
+}
+
+message SyncResponse {}