gmscompat: Dynamically spoof props for GMS
* since goolag is on rampage of killing working build FPs
Change-Id: Ib920ba0b404382344e23b9763251b33ba275634b
diff --git a/core/java/com/android/internal/gmscompat/AttestationHooks.java b/core/java/com/android/internal/gmscompat/AttestationHooks.java
index 314264a..42cd62a 100644
--- a/core/java/com/android/internal/gmscompat/AttestationHooks.java
+++ b/core/java/com/android/internal/gmscompat/AttestationHooks.java
@@ -1,17 +1,9 @@
/*
* Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The LeafOS Project
*
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
+ * SPDX-License-Identifier: Apache-2.0
*
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
*/
package com.android.internal.gmscompat;
@@ -19,22 +11,35 @@
import android.app.Application;
import android.content.Context;
import android.os.Build;
+import android.os.Environment;
+import android.os.SystemProperties;
import android.text.TextUtils;
import android.util.Log;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Arrays;
+import java.util.Iterator;
/** @hide */
public final class AttestationHooks {
- private static final String TAG = "GmsCompat/Attestation";
+ private static final String TAG = AttestationHooks.class.getSimpleName();
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
private static final String PACKAGE_GMS = "com.google.android.gms";
private static final String PROCESS_UNSTABLE = "com.google.android.gms.unstable";
private static final String SAMSUNG = "com.samsung.android.";
+ private static final String DATA_FILE = "gms_certified_props.json";
private static volatile boolean sIsGms = false;
- private AttestationHooks() { }
+ private AttestationHooks() {}
private static void setBuildField(String key, String value) {
try {
@@ -75,18 +80,42 @@
if (PACKAGE_GMS.equals(packageName)) {
setBuildField("TIME", String.valueOf(System.currentTimeMillis()));
if (PROCESS_UNSTABLE.equals(processName)) {
- sIsGms = true;
- setBuildField("FINGERPRINT", "google/marlin/marlin:7.1.2/NJH47F/4146041:user/release-keys");
- setBuildField("PRODUCT", "marlin");
- setBuildField("DEVICE", "marlin");
- setBuildField("MODEL", "Pixel XL");
+ sIsGms = true;
+ setGmsCertifiedProps();
}
}
- // Samsung apps like SmartThings, Galaxy Wearable crashes on samsung devices running AOSP
+ // Samsung apps like SmartThings, Galaxy Wearable crashes
+ // on samsung devices running AOSP
if (packageName.startsWith(SAMSUNG)) {
- setBuildField("BRAND", "google");
- setBuildField("MANUFACTURER", "google");
+ setBuildField("BRAND", "google");
+ setBuildField("MANUFACTURER", "google");
+ }
+ }
+
+ private static void setGmsCertifiedProps() {
+ File dataFile = new File(Environment.getDataSystemDirectory(), DATA_FILE);
+ String savedProps = readFromFile(dataFile);
+
+ if (TextUtils.isEmpty(savedProps)) {
+ Log.e(TAG, "No props found to spoof");
+ return;
+ }
+
+ dlog("Found props");
+ try {
+ JSONObject parsedProps = new JSONObject(savedProps);
+ Iterator<String> keys = parsedProps.keys();
+
+ while (keys.hasNext()) {
+ String key = keys.next();
+ String value = parsedProps.getString(key);
+ dlog(key + ": " + value);
+
+ setBuildField(key, value);
+ }
+ } catch (JSONException e) {
+ Log.e(TAG, "Error parsing JSON data", e);
}
}
@@ -101,4 +130,25 @@
throw new UnsupportedOperationException();
}
}
+
+ private static String readFromFile(File file) {
+ StringBuilder content = new StringBuilder();
+
+ if (file.exists()) {
+ try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
+ String line;
+
+ while ((line = reader.readLine()) != null) {
+ content.append(line);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error reading from file", e);
+ }
+ }
+ return content.toString();
+ }
+
+ private static void dlog(String message) {
+ if (DEBUG) Log.d(TAG, message);
+ }
}
diff --git a/services/core/java/com/android/server/gmscompat/AttestationService.java b/services/core/java/com/android/server/gmscompat/AttestationService.java
new file mode 100644
index 0000000..4c58167
--- /dev/null
+++ b/services/core/java/com/android/server/gmscompat/AttestationService.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2024 The LeafOS Project
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ */
+
+package com.android.server.gmscompat;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.os.Environment;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.server.SystemService;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URI;
+import java.net.URL;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public final class AttestationService extends SystemService {
+ private static final String TAG = AttestationService.class.getSimpleName();
+ private static final String API = "https://play.leafos.org";
+
+ private static final String DATA_FILE = "gms_certified_props.json";
+
+ private static final long INITIAL_DELAY = 0;
+ private static final long INTERVAL = 5;
+
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private final Context mContext;
+ private final File mDataFile;
+ private final ScheduledExecutorService mScheduler;
+
+ public AttestationService(Context context) {
+ super(context);
+ mContext = context;
+ mDataFile = new File(Environment.getDataSystemDirectory(), DATA_FILE);
+ mScheduler = Executors.newSingleThreadScheduledExecutor();
+ }
+
+ @Override
+ public void onStart() {}
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == PHASE_BOOT_COMPLETED) {
+ Log.i(TAG, "Scheduling the service");
+ mScheduler.scheduleAtFixedRate(
+ new FetchGmsCertifiedProps(), INITIAL_DELAY, INTERVAL, TimeUnit.MINUTES);
+ }
+ }
+
+ private String readFromFile(File file) {
+ StringBuilder content = new StringBuilder();
+
+ if (file.exists()) {
+ try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
+ String line;
+
+ while ((line = reader.readLine()) != null) {
+ content.append(line);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error reading from file", e);
+ }
+ }
+ return content.toString();
+ }
+
+ private void writeToFile(File file, String data) {
+ try (FileWriter writer = new FileWriter(file)) {
+ writer.write(data);
+ // Set -rw-r--r-- (644) permission to make it readable by others.
+ file.setReadable(true, false);
+ } catch (IOException e) {
+ Log.e(TAG, "Error writing to file", e);
+ }
+ }
+
+ private String fetchProps() {
+ try {
+ URL url = new URI(API).toURL();
+ HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
+
+ try {
+ urlConnection.setConnectTimeout(10000);
+ urlConnection.setReadTimeout(10000);
+
+ try (BufferedReader reader =
+ new BufferedReader(new InputStreamReader(urlConnection.getInputStream()))) {
+ StringBuilder response = new StringBuilder();
+ String line;
+
+ while ((line = reader.readLine()) != null) {
+ response.append(line);
+ }
+
+ return response.toString();
+ }
+ } finally {
+ urlConnection.disconnect();
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error making an API request", e);
+ return null;
+ }
+ }
+
+ private boolean isInternetConnected() {
+ ConnectivityManager cm =
+ (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+ Network nw = cm.getActiveNetwork();
+ if (nw == null) return false;
+ NetworkCapabilities actNw = cm.getNetworkCapabilities(nw);
+ return actNw != null
+ && (actNw.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
+ || actNw.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)
+ || actNw.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
+ || actNw.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH));
+ }
+
+ private void dlog(String message) {
+ if (DEBUG) Log.d(TAG, message);
+ }
+
+ private class FetchGmsCertifiedProps implements Runnable {
+ @Override
+ public void run() {
+ try {
+ dlog("FetchGmsCertifiedProps started");
+
+ if (!isInternetConnected()) {
+ Log.e(TAG, "Internet unavailable");
+ return;
+ }
+
+ String savedProps = readFromFile(mDataFile);
+ String props = fetchProps();
+
+ if (props != null && !savedProps.equals(props)) {
+ dlog("Found new props");
+ writeToFile(mDataFile, props);
+ dlog("FetchGmsCertifiedProps completed");
+ } else {
+ dlog("No change in props");
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error in FetchGmsCertifiedProps", e);
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index bedf40a..268023e 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -137,6 +137,7 @@
import com.android.server.dreams.DreamManagerService;
import com.android.server.emergency.EmergencyAffordanceService;
import com.android.server.flags.FeatureFlagsService;
+import com.android.server.gmscompat.AttestationService;
import com.android.server.gpu.GpuService;
import com.android.server.grammaticalinflection.GrammaticalInflectionService;
import com.android.server.graphics.fonts.FontManagerService;
@@ -3280,6 +3281,10 @@
}
t.traceEnd();
+ // AttestationService
+ t.traceBegin("AttestationService");
+ mSystemServiceManager.startService(AttestationService.class);
+ t.traceEnd();
t.traceEnd(); // startOtherServices
}