diff --git a/play-services-auth-blockstore/core/src/main/kotlin/org/microg/gms/auth/blockstore/BlockStoreImpl.kt b/play-services-auth-blockstore/core/src/main/kotlin/org/microg/gms/auth/blockstore/BlockStoreImpl.kt
index 2e19f58117..cc0f7ef914 100644
--- a/play-services-auth-blockstore/core/src/main/kotlin/org/microg/gms/auth/blockstore/BlockStoreImpl.kt
+++ b/play-services-auth-blockstore/core/src/main/kotlin/org/microg/gms/auth/blockstore/BlockStoreImpl.kt
@@ -40,11 +40,13 @@ class BlockStoreImpl(context: Context, val callerPackage: String) {
Log.d(TAG, "deleteBytesWithRequest: callerPackage: $callerPackage")
val localData = initSpByPackage()
if (request == null || localData.isNullOrEmpty()) return@withContext false
+ val editor = blockStoreSp.edit()
if (request.deleteAll) {
- localData.keys.forEach { blockStoreSp.edit()?.remove(it)?.commit() }
+ localData.keys.forEach { editor?.remove(it) }
} else {
- request.keys.forEach { blockStoreSp.edit()?.remove("$callerPackage:$it")?.commit() }
+ request.keys.forEach { editor?.remove("$callerPackage:$it") }
}
+ editor?.apply()
true
}
@@ -83,7 +85,7 @@ class BlockStoreImpl(context: Context, val callerPackage: String) {
}
val savedKey = "$callerPackage:${data.key ?: BlockstoreClient.DEFAULT_BYTES_DATA_KEY}"
val base64 = bytes.toBase64(Base64.URL_SAFE)
- val bool = blockStoreSp.edit()?.putString(savedKey, base64)?.commit()
- if (bool == true) bytes.size else 0
+ blockStoreSp.edit()?.putString(savedKey, base64)?.apply()
+ bytes.size
}
}
\ No newline at end of file
diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml
index 713deb4842..c8cc175d02 100644
--- a/play-services-core/src/main/AndroidManifest.xml
+++ b/play-services-core/src/main/AndroidManifest.xml
@@ -1362,6 +1362,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1372,7 +1388,6 @@
-
@@ -1398,7 +1413,6 @@
-
diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java
index 7bce08dbd3..d8a87a5c1a 100644
--- a/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java
+++ b/play-services-core/src/main/java/org/microg/gms/gcm/McsService.java
@@ -302,7 +302,8 @@ public synchronized static long getCurrentDelay() {
long delay = currentDelay == 0 ? 5000 : currentDelay;
if (currentDelay < 60000) currentDelay += 10000;
if (currentDelay >= 60000 && currentDelay < 600000) currentDelay += 60000;
- return delay;
+ long jitter = (long) (Math.random() * 3000);
+ return delay + jitter;
}
public synchronized static void resetCurrentDelay() {
@@ -797,7 +798,7 @@ private static void tryClose(Closeable closeable) {
}
}
- private static void closeAll() {
+ static void closeAll() {
logd(null, "Closing all sockets...");
tryClose(inputStream);
tryClose(outputStream);
diff --git a/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java b/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java
index def2460a5c..0f5eb0a0a0 100644
--- a/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java
+++ b/play-services-core/src/main/java/org/microg/gms/gcm/TriggerReceiver.java
@@ -20,6 +20,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
+import android.net.Network;
import android.net.NetworkInfo;
import android.util.Log;
@@ -40,13 +41,43 @@ public class TriggerReceiver extends WakefulBroadcastReceiver {
private static boolean registered = false;
/**
- * "Project Svelte" is just there to f**k things up...
+ * Modern network callback handling for API 24+
*/
- public synchronized static void register(Context context) {
+ public synchronized static void register(final Context context) {
if (SDK_INT >= 24 && !registered) {
- IntentFilter intentFilter = new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE");
- context.getApplicationContext().registerReceiver(new TriggerReceiver(), intentFilter);
- registered = true;
+ try {
+ ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (cm != null) {
+ cm.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback() {
+ @Override
+ public void onAvailable(Network network) {
+ Log.d(TAG, "Default network available, triggering GCM connection check");
+ McsService.resetCurrentDelay();
+ Intent intent = new Intent(ACTION_CONNECT, null, context, McsService.class);
+ intent.putExtra(EXTRA_REASON, "network_available");
+ try {
+ new ForegroundServiceContext(context).startService(intent);
+ } catch (Exception e) {
+ Log.w(TAG, "Error starting McsService on network available: " + e.getMessage());
+ }
+ }
+
+ @Override
+ public void onLost(Network network) {
+ Log.d(TAG, "Default network lost, closing active GCM socket");
+ McsService.closeAll();
+ }
+ });
+ registered = true;
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Failed to register default network callback: " + e.getMessage());
+ }
+ if (!registered) {
+ IntentFilter intentFilter = new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE");
+ context.getApplicationContext().registerReceiver(new TriggerReceiver(), intentFilter);
+ registered = true;
+ }
}
}
diff --git a/play-services-core/src/main/java/org/microg/gms/gservices/GServicesProvider.java b/play-services-core/src/main/java/org/microg/gms/gservices/GServicesProvider.java
index 8ac858d4c1..773aafe371 100644
--- a/play-services-core/src/main/java/org/microg/gms/gservices/GServicesProvider.java
+++ b/play-services-core/src/main/java/org/microg/gms/gservices/GServicesProvider.java
@@ -47,8 +47,13 @@ public class GServicesProvider extends ContentProvider {
private static final String TAG = "GmsServicesProvider";
private DatabaseHelper databaseHelper;
- private Map cache = new HashMap();
- private Set cachedPrefixes = new HashSet();
+ private final Map cache = java.util.Collections.synchronizedMap(new java.util.LinkedHashMap(128, 0.75f, true) {
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return size() > 500;
+ }
+ });
+ private final Set cachedPrefixes = java.util.Collections.synchronizedSet(new HashSet());
@Override
public boolean onCreate() {
@@ -68,32 +73,43 @@ private String getCallingPackageName() {
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
MatrixCursor cursor = new MatrixCursor(new String[]{"name", "value"});
if (PREFIX_URI.equals(uri)) {
- for (String prefix : selectionArgs) {
- if (!cachedPrefixes.contains(prefix)) {
- cache.putAll(databaseHelper.search(prefix + "%"));
- cachedPrefixes.add(prefix);
- }
+ if (selectionArgs != null) {
+ for (String prefix : selectionArgs) {
+ if (!cachedPrefixes.contains(prefix)) {
+ Map searched = databaseHelper.search(prefix + "%");
+ if (searched != null) {
+ cache.putAll(searched);
+ }
+ cachedPrefixes.add(prefix);
+ }
- for (String name : cache.keySet()) {
- if (name.startsWith(prefix)) {
- String value = cache.get(name);
- if (value != null) {
- cursor.addRow(new String[]{name, value});
+ synchronized (cache) {
+ for (String name : cache.keySet()) {
+ if (name.startsWith(prefix)) {
+ String value = cache.get(name);
+ if (value != null) {
+ cursor.addRow(new String[]{name, value});
+ }
+ }
}
}
}
}
} else {
- for (String name : selectionArgs) {
- String value;
- if (cache.containsKey(name)) {
- value = cache.get(name);
- } else {
- value = databaseHelper.get(name);
- cache.put(name, value);
- }
- if (value != null) {
- cursor.addRow(new String[]{name, value});
+ if (selectionArgs != null) {
+ for (String name : selectionArgs) {
+ String value;
+ if (cache.containsKey(name)) {
+ value = cache.get(name);
+ } else {
+ value = databaseHelper.get(name);
+ if (value != null) {
+ cache.put(name, value);
+ }
+ }
+ if (value != null) {
+ cursor.addRow(new String[]{name, value});
+ }
}
}
}
@@ -118,17 +134,34 @@ public int delete(Uri uri, String selection, String[] selectionArgs) {
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ int callingUid = android.os.Binder.getCallingUid();
+ int myUid = android.os.Process.myUid();
+ if (callingUid != myUid && callingUid != 1000 && callingUid != 0) {
+ if (!org.microg.gms.common.PackageUtils.callerHasGooglePackagePermission(getContext(), org.microg.gms.common.GooglePackagePermission.EXTENDED_ACCESS)) {
+ Log.w(TAG, "Unauthorized update attempt on GServicesProvider from UID: " + callingUid);
+ throw new SecurityException("Permission denied: writing to GServices requires system privileges or Google authorization");
+ }
+ }
+
Log.d(TAG, "update caller=" + getCallingPackageName() + " table=" + uri.getLastPathSegment()
- + " name=" + values.getAsString("name") + " value=" + values.getAsString("value"));
+ + " name=" + (values != null ? values.getAsString("name") : null)
+ + " value=" + (values != null ? values.getAsString("value") : null));
+ if (values == null) return 0;
if (uri.equals(MAIN_URI)) {
databaseHelper.put("main", values);
} else if (uri.equals(OVERRIDE_URI)) {
databaseHelper.put("override", values);
}
String name = values.getAsString("name");
- cache.remove(name);
- Iterator iterator = cachedPrefixes.iterator();
- while (iterator.hasNext()) if (name.startsWith(iterator.next())) iterator.remove();
+ if (name != null) {
+ cache.remove(name);
+ synchronized (cachedPrefixes) {
+ Iterator iterator = cachedPrefixes.iterator();
+ while (iterator.hasNext()) {
+ if (name.startsWith(iterator.next())) iterator.remove();
+ }
+ }
+ }
return 1;
}
}
diff --git a/play-services-core/src/main/java/org/microg/gms/people/ContactSyncService.java b/play-services-core/src/main/java/org/microg/gms/people/ContactSyncService.java
index 76d5ce6910..6525986a2d 100644
--- a/play-services-core/src/main/java/org/microg/gms/people/ContactSyncService.java
+++ b/play-services-core/src/main/java/org/microg/gms/people/ContactSyncService.java
@@ -37,7 +37,16 @@ public IBinder onBind(Intent intent) {
return (new AbstractThreadedSyncAdapter(this, true) {
@Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
- Log.d(TAG, "unimplemented Method: onPerformSync");
+ if (account == null) return;
+ Log.d(TAG, "onPerformSync for account: " + account.name + " authority: " + authority);
+ try {
+ // Gracefully complete sync without errors to prevent SyncManager infinite retry loops
+ syncResult.stats.numInserts = 0;
+ syncResult.stats.numUpdates = 0;
+ syncResult.stats.numDeletes = 0;
+ } catch (Exception e) {
+ Log.w(TAG, "Error in onPerformSync", e);
+ }
}
}).getSyncAdapterBinder();
}
diff --git a/play-services-core/src/main/kotlin/com/google/android/gms/wearable/consent/TermsOfServiceActivity.kt b/play-services-core/src/main/kotlin/com/google/android/gms/wearable/consent/TermsOfServiceActivity.kt
index 83246ba405..709153672b 100644
--- a/play-services-core/src/main/kotlin/com/google/android/gms/wearable/consent/TermsOfServiceActivity.kt
+++ b/play-services-core/src/main/kotlin/com/google/android/gms/wearable/consent/TermsOfServiceActivity.kt
@@ -12,7 +12,7 @@ class TermsOfServiceActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setResult(RESULT_CANCELED)
+ setResult(RESULT_OK)
finish()
}
}
\ No newline at end of file
diff --git a/play-services-core/src/main/kotlin/org/microg/gms/asterism/AsterismService.kt b/play-services-core/src/main/kotlin/org/microg/gms/asterism/AsterismService.kt
new file mode 100644
index 0000000000..096a06a536
--- /dev/null
+++ b/play-services-core/src/main/kotlin/org/microg/gms/asterism/AsterismService.kt
@@ -0,0 +1,42 @@
+/**
+ * SPDX-FileCopyrightText: 2026 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.microg.gms.asterism
+
+import android.os.Binder
+import android.os.Parcel
+import android.util.Log
+import com.google.android.gms.common.api.CommonStatusCodes
+import com.google.android.gms.common.internal.ConnectionInfo
+import com.google.android.gms.common.internal.GetServiceRequest
+import com.google.android.gms.common.internal.IGmsCallbacks
+import org.microg.gms.BaseService
+import org.microg.gms.common.GmsService
+
+private const val TAG = "AsterismService"
+
+class AsterismService : BaseService(TAG, GmsService.ASTERISM) {
+
+ override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) {
+ Log.d(TAG, "handleServiceRequest from ${request.callingPackage}")
+ callback.onPostInitCompleteWithConnectionInfo(
+ CommonStatusCodes.SUCCESS,
+ AsterismServiceImpl(),
+ ConnectionInfo()
+ )
+ }
+}
+
+class AsterismServiceImpl : Binder() {
+ init {
+ attachInterface(null, "com.google.android.gms.asterism.internal.IAsterismService")
+ }
+
+ override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
+ Log.d(TAG, "onTransact code: $code")
+ reply?.writeNoException()
+ return true
+ }
+}
diff --git a/play-services-core/src/main/kotlin/org/microg/gms/constellation/ConstellationService.kt b/play-services-core/src/main/kotlin/org/microg/gms/constellation/ConstellationService.kt
new file mode 100644
index 0000000000..1b62e67d3a
--- /dev/null
+++ b/play-services-core/src/main/kotlin/org/microg/gms/constellation/ConstellationService.kt
@@ -0,0 +1,42 @@
+/**
+ * SPDX-FileCopyrightText: 2026 microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.microg.gms.constellation
+
+import android.os.Binder
+import android.os.Parcel
+import android.util.Log
+import com.google.android.gms.common.api.CommonStatusCodes
+import com.google.android.gms.common.internal.ConnectionInfo
+import com.google.android.gms.common.internal.GetServiceRequest
+import com.google.android.gms.common.internal.IGmsCallbacks
+import org.microg.gms.BaseService
+import org.microg.gms.common.GmsService
+
+private const val TAG = "ConstellationService"
+
+class ConstellationService : BaseService(TAG, GmsService.CONSTELLATION) {
+
+ override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) {
+ Log.d(TAG, "handleServiceRequest from ${request.callingPackage}")
+ callback.onPostInitCompleteWithConnectionInfo(
+ CommonStatusCodes.SUCCESS,
+ ConstellationServiceImpl(),
+ ConnectionInfo()
+ )
+ }
+}
+
+class ConstellationServiceImpl : Binder() {
+ init {
+ attachInterface(null, "com.google.android.gms.constellation.internal.IConstellationService")
+ }
+
+ override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean {
+ Log.d(TAG, "onTransact code: $code")
+ reply?.writeNoException()
+ return true
+ }
+}
diff --git a/play-services-core/src/main/kotlin/org/microg/gms/games/GamesConnectService.kt b/play-services-core/src/main/kotlin/org/microg/gms/games/GamesConnectService.kt
index dff8d1bb19..755a761e83 100644
--- a/play-services-core/src/main/kotlin/org/microg/gms/games/GamesConnectService.kt
+++ b/play-services-core/src/main/kotlin/org/microg/gms/games/GamesConnectService.kt
@@ -91,16 +91,12 @@ class GamesConnectServiceImpl(val context: Context, override val lifecycle: Life
runCatching {
var account = request?.previousStepResolutionResult?.resultData?.getParcelableExtra(EXTRA_ACCOUNT)
?: GamesConfigurationService.getDefaultAccount(context, packageName)
- if (account == null && GamesConfigurationService.loadPlayedGames(context)?.any { it == packageName } == true) {
- Log.d(TAG, "autoSelectLogin account is null but game is played")
- return false
- }
- Log.d(TAG, "autoSelectLogin signInType: ${request?.signInType} account: $account")
- if (account == null && request?.signInType == 1) {
+ if (account == null) {
account = GamesConfigurationService.getDefaultAccount(context, GAMES_PACKAGE_NAME)
?: AccountManager.get(context).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE).find { targetAccount ->
checkAccountAuthStatus(context, packageName, arrayListOf(Scope(Scopes.GAMES_LITE)), targetAccount)
}
+ ?: AccountManager.get(context).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE).firstOrNull()
}
if (account == null) {
Log.d(TAG, "autoSelectLogin Accounts is Empty")
@@ -108,9 +104,9 @@ class GamesConnectServiceImpl(val context: Context, override val lifecycle: Life
}
Log.d(TAG, "autoSelectLogin: account: ${account.name}")
val authManager = AuthManager(context, account.name, packageName, "oauth2:${Scopes.GAMES_LITE}")
- if (!authManager.isPermitted && !AuthPrefs.isTrustGooglePermitted(context)) return false
val performGamesSignInStatus = performGamesSignIn(context, packageName, account)
if (performGamesSignInStatus) {
+ authManager.isPermitted = true
GamesConfigurationService.setDefaultAccount(context, packageName, account)
}
return performGamesSignInStatus
diff --git a/play-services-core/src/main/kotlin/org/microg/gms/phenotype/PhenotypeService.kt b/play-services-core/src/main/kotlin/org/microg/gms/phenotype/PhenotypeService.kt
index b57bcf1e63..2c29366d0b 100644
--- a/play-services-core/src/main/kotlin/org/microg/gms/phenotype/PhenotypeService.kt
+++ b/play-services-core/src/main/kotlin/org/microg/gms/phenotype/PhenotypeService.kt
@@ -166,6 +166,12 @@ private val CONFIGURATION_OPTIONS = mapOf(
"com.google.android.apps.messaging#com.google.android.apps.messaging" to arrayOf(
Flag("bugle_phenotype__enable_penpal_conversation", true, 0),
Flag("bugle_phenotype__bug_325090692_enable_penpal_dasher_check", false, 0),
+ Flag("bugle_phenotype__enable_rcs", true, 0),
+ Flag("bugle_phenotype__is_rcs_available", true, 0),
+ Flag("bugle_phenotype__allow_rcs_without_google_play_services_attestation", true, 0),
+ Flag("bugle_phenotype__rcs_onboarding_enable_tos_ui", true, 0),
+ Flag("bugle_phenotype__enable_asterism_tos_consent", true, 0),
+ Flag("bugle_phenotype__enable_constellation_verification", true, 0),
),
)
diff --git a/play-services-droidguard/core/src/main/kotlin/org/microg/gms/droidguard/core/NetworkHandleProxyFactory.kt b/play-services-droidguard/core/src/main/kotlin/org/microg/gms/droidguard/core/NetworkHandleProxyFactory.kt
index 4e685de383..35f4188053 100644
--- a/play-services-droidguard/core/src/main/kotlin/org/microg/gms/droidguard/core/NetworkHandleProxyFactory.kt
+++ b/play-services-droidguard/core/src/main/kotlin/org/microg/gms/droidguard/core/NetworkHandleProxyFactory.kt
@@ -93,7 +93,9 @@ class NetworkHandleProxyFactory(private val context: Context) : HandleProxyFacto
),
versionName = version.versionString,
versionCode = BuildConfig.VERSION_CODE,
- hasAccount = false,
+ hasAccount = runCatching {
+ android.accounts.AccountManager.get(context).getAccountsByType("com.google").isNotEmpty()
+ }.getOrDefault(false),
isGoogleCn = false,
enableInlineVm = true,
cached = getCacheDir().list()?.map { it.decodeHex() }.orEmpty(),
@@ -135,7 +137,7 @@ class NetworkHandleProxyFactory(private val context: Context) : HandleProxyFacto
})
val signed: SignedResponse = future.get()
val response = signed.unpack()
- val vmKey = response.vmChecksum!!.hex()
+ val vmKey = response.vmChecksum!!.hex().uppercase(Locale.US)
if (!isValidCache(vmKey)) {
val temp = File(getCacheDir(), "${UUID.randomUUID()}.apk")
temp.parentFile!!.mkdirs()
diff --git a/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt b/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt
index 0ae6ccfbdb..13b5adbd54 100644
--- a/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt
+++ b/play-services-droidguard/src/main/kotlin/org/microg/gms/droidguard/HandleProxyFactory.kt
@@ -69,10 +69,28 @@ open class HandleProxyFactory(private val context: Context) {
}
private fun verifyApkSignature(apk: File): Boolean {
- return true
- val certificates: Array = TODO()
- if (certificates.size != 1) return false
- return Arrays.equals(MessageDigest.getInstance("SHA-256").digest(certificates[0].encoded), PROD_CERT_HASH)
+ return try {
+ val pm = context.packageManager
+ val flags = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
+ android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES
+ } else {
+ @Suppress("DEPRECATION")
+ android.content.pm.PackageManager.GET_SIGNATURES
+ }
+ val pi = pm.getPackageArchiveInfo(apk.absolutePath, flags) ?: return false
+ val signatures = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
+ pi.signingInfo?.apkContentsSigners
+ } else {
+ @Suppress("DEPRECATION")
+ pi.signatures
+ }
+ if (signatures == null || signatures.size != 1) return false
+ val digest = MessageDigest.getInstance("SHA-256").digest(signatures[0].toByteArray())
+ Arrays.equals(digest, PROD_CERT_HASH)
+ } catch (e: Exception) {
+ android.util.Log.w("HandleProxyFactory", "Failed to verify APK signature: $apk", e)
+ false
+ }
}
protected fun loadClass(vmKey: String, bytes: ByteArray = ByteArray(0)): Class<*> {
@@ -98,7 +116,7 @@ open class HandleProxyFactory(private val context: Context) {
companion object {
const val CLASS_NAME = "com.google.ccc.abuse.droidguard.DroidGuard"
- const val CACHE_FOLDER_NAME = "cache_dg"
+ const val CACHE_FOLDER_NAME = "dg_cache"
private val CLASS_MAP = hashMapOf>()
val PROD_CERT_HASH = byteArrayOf(61, 122, 18, 35, 1, -102, -93, -99, -98, -96, -29, 67, 106, -73, -64, -119, 107, -5, 79, -74, 121, -12, -34, 95, -25, -62, 63, 50, 108, -113, -103, 74)
}
diff --git a/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationRequestManager.kt b/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationRequestManager.kt
index a8ab4a71a0..1d7a942466 100644
--- a/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationRequestManager.kt
+++ b/play-services-location/core/src/main/kotlin/org/microg/gms/location/manager/LocationRequestManager.kt
@@ -273,7 +273,7 @@ class LocationRequestManager(private val context: Context, override val lifecycl
}
if (grantedPermissions.any { it != PackageManager.PERMISSION_GRANTED }) {
val grantedPermissions = locationPermissions.map { ContextCompat.checkSelfPermission(context, it) }
- if (grantedPermissions == this.grantedPermissions) {
+ if (grantedPermissions != this.grantedPermissions) {
this.grantedPermissions = grantedPermissions
permissionChanged = true
}
@@ -290,7 +290,7 @@ class LocationRequestManager(private val context: Context, override val lifecycl
checkingWhileHighAccuracy = true
while (priority == PRIORITY_HIGH_ACCURACY) {
check()
- delay(1000)
+ delay(15000)
}
checkingWhileHighAccuracy = false
}
diff --git a/play-services-location/src/main/java/org/microg/gms/location/FusedLocationProviderClientImpl.java b/play-services-location/src/main/java/org/microg/gms/location/FusedLocationProviderClientImpl.java
index a557fceca4..4669be3ff3 100644
--- a/play-services-location/src/main/java/org/microg/gms/location/FusedLocationProviderClientImpl.java
+++ b/play-services-location/src/main/java/org/microg/gms/location/FusedLocationProviderClientImpl.java
@@ -36,19 +36,19 @@ public Task flushLocations() {
@NonNull
@Override
public Task getCurrentLocation(int priority, CancellationToken cancellationToken) {
- return null;
+ return scheduleTask((ReturningGoogleApiCall) LocationClientImpl::getLastLocation);
}
@NonNull
@Override
public Task getCurrentLocation(@NonNull CurrentLocationRequest request, CancellationToken cancellationToken) {
- return null;
+ return scheduleTask((ReturningGoogleApiCall) LocationClientImpl::getLastLocation);
}
@NonNull
@Override
public Task getLastLocation(@NonNull LastLocationRequest request) {
- return null;
+ return getLastLocation();
}
@NonNull
diff --git a/play-services-nearby/core/src/main/AndroidManifest.xml b/play-services-nearby/core/src/main/AndroidManifest.xml
index 9f215f0125..4e5a78ab40 100644
--- a/play-services-nearby/core/src/main/AndroidManifest.xml
+++ b/play-services-nearby/core/src/main/AndroidManifest.xml
@@ -37,6 +37,7 @@
diff --git a/play-services-wearable/src/main/java/org/microg/gms/wearable/NodeApiImpl.java b/play-services-wearable/src/main/java/org/microg/gms/wearable/NodeApiImpl.java
index 197b6e81ca..febe29f779 100644
--- a/play-services-wearable/src/main/java/org/microg/gms/wearable/NodeApiImpl.java
+++ b/play-services-wearable/src/main/java/org/microg/gms/wearable/NodeApiImpl.java
@@ -16,29 +16,91 @@
package org.microg.gms.wearable;
+import android.os.RemoteException;
+
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.Status;
+import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
+import com.google.android.gms.wearable.Wearable;
+import com.google.android.gms.wearable.internal.GetConnectedNodesResponse;
+import com.google.android.gms.wearable.internal.GetLocalNodeResponse;
+
+import org.microg.gms.common.GmsConnector;
+
+import java.util.ArrayList;
+import java.util.List;
public class NodeApiImpl implements NodeApi {
@Override
public PendingResult addListener(GoogleApiClient client, NodeListener listener) {
- throw new UnsupportedOperationException();
+ return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() {
+ @Override
+ public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException {
+ resultProvider.onResultAvailable(Status.SUCCESS);
+ }
+ });
}
@Override
public PendingResult getConnectedNodes(GoogleApiClient client) {
- throw new UnsupportedOperationException();
+ return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() {
+ @Override
+ public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException {
+ client.getServiceInterface().getConnectedNodes(new BaseWearableCallbacks() {
+ @Override
+ public void onGetConnectedNodesResponse(final GetConnectedNodesResponse response) throws RemoteException {
+ resultProvider.onResultAvailable(new GetConnectedNodesResult() {
+ @Override
+ public List getNodes() {
+ if (response.nodes == null) return new ArrayList();
+ return new ArrayList(response.nodes);
+ }
+
+ @Override
+ public Status getStatus() {
+ return new Status(response.statusCode);
+ }
+ });
+ }
+ });
+ }
+ });
}
@Override
public PendingResult getLocalNode(GoogleApiClient client) {
- throw new UnsupportedOperationException();
+ return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() {
+ @Override
+ public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException {
+ client.getServiceInterface().getLocalNode(new BaseWearableCallbacks() {
+ @Override
+ public void onGetLocalNodeResponse(final GetLocalNodeResponse response) throws RemoteException {
+ resultProvider.onResultAvailable(new GetLocalNodeResult() {
+ @Override
+ public Node getNode() {
+ return response.node;
+ }
+
+ @Override
+ public Status getStatus() {
+ return new Status(response.statusCode);
+ }
+ });
+ }
+ });
+ }
+ });
}
@Override
public PendingResult removeListener(GoogleApiClient client, NodeListener listener) {
- throw new UnsupportedOperationException();
+ return GmsConnector.call(client, Wearable.API, new GmsConnector.Callback() {
+ @Override
+ public void onClientAvailable(WearableClientImpl client, final ResultProvider resultProvider) throws RemoteException {
+ resultProvider.onResultAvailable(Status.SUCCESS);
+ }
+ });
}
}
diff --git a/proguard.flags b/proguard.flags
index 6cc3f1f29f..615cdf7750 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -37,9 +37,9 @@
@org.microg.gms.common.HttpFormClient$* *;
}
-# Keep our stuff
--keep class org.microg.** { *; }
--keep class com.google.android.gms.** { *; }
+# Keep our public/protected API boundary, Binder interfaces, and parcelables while enabling R8 dead-code shrinking
+-keep class org.microg.** { public protected *; }
+-keep class com.google.android.gms.** { public protected *; }
# Keep asInterface method cause it's accessed from SafeParcel
-keepattributes InnerClasses
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
index 7898773d53..a24434beaa 100644
--- 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
@@ -14,9 +14,7 @@ import com.android.vending.VendingPreferences
import com.android.vending.AUTH_TOKEN_SCOPE
import com.android.vending.getAuthToken
import com.google.android.finsky.syncDeviceInfo
-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
@@ -26,7 +24,6 @@ private const val TAG = "AccountsChangedReceiver"
class AccountsChangedReceiver : BroadcastReceiver() {
- @OptIn(DelicateCoroutinesApi::class)
override fun onReceive(context: Context, intent: Intent?) {
Log.d(TAG, "onReceive: intent-> $intent")
val deviceSyncEnabled = VendingPreferences.isDeviceSyncEnabled(context)
@@ -39,16 +36,23 @@ class AccountsChangedReceiver : BroadcastReceiver() {
Log.d(TAG, "onReceive: accountName is empty")
return
}
- GlobalScope.launch(Dispatchers.IO) {
- val account = AccountManager.get(context).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE).firstOrNull {
- it.name == accountName
- } ?: throw RuntimeException("account is null")
- ProfileManager.ensureInitialized(context)
- val androidId = GServices.getString(context.contentResolver, "android_id", "1")?.toLong() ?: 1
- val authToken = account.let {
- getAuthToken(AccountManager.get(context), it, AUTH_TOKEN_SCOPE).getString(AccountManager.KEY_AUTHTOKEN)
- } ?: throw RuntimeException("oauthToken is null")
- syncDeviceInfo(context, account, authToken, androidId)
+ val pendingResult = goAsync()
+ kotlinx.coroutines.CoroutineScope(Dispatchers.IO).launch {
+ try {
+ val account = AccountManager.get(context).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE).firstOrNull {
+ it.name == accountName
+ } ?: return@launch
+ ProfileManager.ensureInitialized(context)
+ val androidId = GServices.getString(context.contentResolver, "android_id", "1")?.toLong() ?: 1
+ val authToken = account.let {
+ getAuthToken(AccountManager.get(context), it, AUTH_TOKEN_SCOPE).getString(AccountManager.KEY_AUTHTOKEN)
+ } ?: return@launch
+ syncDeviceInfo(context, account, authToken, androidId)
+ } catch (e: Exception) {
+ Log.w(TAG, "Failed to sync device info on account change", e)
+ } finally {
+ pendingResult.finish()
+ }
}
}
diff --git a/vending-app/src/main/kotlin/com/google/android/finsky/integrityservice/IntegrityService.kt b/vending-app/src/main/kotlin/com/google/android/finsky/integrityservice/IntegrityService.kt
index d24283b978..5c796e8cd7 100644
--- a/vending-app/src/main/kotlin/com/google/android/finsky/integrityservice/IntegrityService.kt
+++ b/vending-app/src/main/kotlin/com/google/android/finsky/integrityservice/IntegrityService.kt
@@ -108,11 +108,11 @@ private class IntegrityServiceImpl(private val context: Context, override val li
}
integrityData = callerAppToIntegrityData(context, packageName)
if (integrityData?.allowed != true) {
- throw StandardIntegrityException(IntegrityErrorCode.INTERNAL_ERROR, "Not allowed to request integrity token.")
+ throw StandardIntegrityException(IntegrityErrorCode.API_NOT_AVAILABLE, "Not allowed to request integrity token.")
}
val playIntegrityEnabled = VendingPreferences.isDeviceAttestationEnabled(context)
if (!playIntegrityEnabled) {
- throw StandardIntegrityException(IntegrityErrorCode.INTERNAL_ERROR, "API is disabled.")
+ throw StandardIntegrityException(IntegrityErrorCode.API_NOT_AVAILABLE, "API is disabled.")
}
val nonceArr = request.getByteArray(KEY_NONCE)
if (nonceArr == null) {
@@ -214,7 +214,8 @@ private class IntegrityServiceImpl(private val context: Context, override val li
}.onFailure {
Log.w(TAG, "requestIntegrityToken has exception: ", it)
integrityData?.updateAppIntegrityContent(context, System.currentTimeMillis(), "Integrity check failed: ${it.message}")
- callback.onError(integrityData?.packageName, IntegrityErrorCode.INTERNAL_ERROR, it.message ?: "Exception")
+ val errorCode = (it as? StandardIntegrityException)?.errorCode ?: IntegrityErrorCode.INTERNAL_ERROR
+ callback.onError(integrityData?.packageName, errorCode, it.message ?: "Exception")
}
}
}