Compare commits
4 commits
83c865fd13
...
82edcb6260
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82edcb6260 | ||
|
|
b9537c6a51 | ||
|
|
d9682eafe0 | ||
|
|
a8011f7594 |
8 changed files with 803 additions and 0 deletions
|
|
@ -834,8 +834,104 @@ public class ApplicationPackageManager extends PackageManager {
|
|||
}
|
||||
};
|
||||
|
||||
private static final String[] pTensorCodenames = {
|
||||
"comet",
|
||||
"komodo",
|
||||
"caiman",
|
||||
"tokay",
|
||||
"akita",
|
||||
"husky",
|
||||
"shiba",
|
||||
"felix",
|
||||
"tangorpro",
|
||||
"lynx",
|
||||
"cheetah",
|
||||
"panther",
|
||||
"bluejay",
|
||||
"oriole",
|
||||
"raven"
|
||||
};
|
||||
|
||||
private static final String[] featuresPixel = {
|
||||
"com.google.android.apps.photos.PIXEL_2019_PRELOAD",
|
||||
"com.google.android.apps.photos.PIXEL_2019_MIDYEAR_PRELOAD",
|
||||
"com.google.android.apps.photos.PIXEL_2018_PRELOAD",
|
||||
"com.google.android.apps.photos.PIXEL_2017_PRELOAD",
|
||||
"com.google.android.feature.PIXEL_2021_MIDYEAR_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_2020_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_2020_MIDYEAR_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_2019_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_2019_MIDYEAR_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_2018_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_2017_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_EXPERIENCE",
|
||||
"com.google.android.feature.GOOGLE_BUILD",
|
||||
"com.google.android.feature.GOOGLE_EXPERIENCE"
|
||||
};
|
||||
|
||||
private static final String[] featuresPixelOthers = {
|
||||
"com.google.android.feature.ASI",
|
||||
"com.google.android.feature.ANDROID_ONE_EXPERIENCE",
|
||||
"com.google.android.feature.GOOGLE_FI_BUNDLED",
|
||||
"com.google.android.feature.LILY_EXPERIENCE",
|
||||
"com.google.android.feature.TURBO_PRELOAD",
|
||||
"com.google.android.feature.WELLBEING",
|
||||
"com.google.lens.feature.IMAGE_INTEGRATION",
|
||||
"com.google.lens.feature.CAMERA_INTEGRATION",
|
||||
"com.google.photos.trust_debug_certs",
|
||||
"com.google.android.feature.AER_OPTIMIZED",
|
||||
"com.google.android.feature.NEXT_GENERATION_ASSISTANT",
|
||||
"android.software.game_service",
|
||||
"com.google.android.feature.EXCHANGE_6_2",
|
||||
"com.google.android.apps.dialer.call_recording_audio",
|
||||
"com.google.android.apps.dialer.SUPPORTED"
|
||||
};
|
||||
|
||||
private static final String[] featuresTensor = {
|
||||
"com.google.android.feature.PIXEL_2025_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_2025_MIDYEAR_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_2024_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_2024_MIDYEAR_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_2023_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_2023_MIDYEAR_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_2022_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_2022_MIDYEAR_EXPERIENCE",
|
||||
"com.google.android.feature.PIXEL_2021_EXPERIENCE"
|
||||
};
|
||||
|
||||
private static final String[] featuresNexus = {
|
||||
"com.google.android.apps.photos.NEXUS_PRELOAD",
|
||||
"com.google.android.apps.photos.nexus_preload",
|
||||
"com.google.android.feature.PIXEL_EXPERIENCE",
|
||||
"com.google.android.feature.GOOGLE_BUILD",
|
||||
"com.google.android.feature.GOOGLE_EXPERIENCE"
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean hasSystemFeature(String name, int version) {
|
||||
String packageName = ActivityThread.currentPackageName();
|
||||
if (packageName != null
|
||||
&& (packageName.equals("com.google.android.googlequicksearchbox")
|
||||
|| packageName.equals("com.google.android.apps.nexuslauncher"))) {
|
||||
if (Arrays.asList(featuresPixel).contains(name)) return true;
|
||||
if (Arrays.asList(featuresPixelOthers).contains(name)) return true;
|
||||
if (Arrays.asList(featuresTensor).contains(name)) return true;
|
||||
if (Arrays.asList(featuresNexus).contains(name)) return true;
|
||||
}
|
||||
if (packageName != null
|
||||
&& packageName.equals("com.google.android.apps.photos")
|
||||
&& SystemProperties.getBoolean("persist.sys.pixelprops.gphotos", true)) {
|
||||
if (Arrays.asList(featuresPixel).contains(name)) return false;
|
||||
if (Arrays.asList(featuresPixelOthers).contains(name)) return true;
|
||||
if (Arrays.asList(featuresTensor).contains(name)) return false;
|
||||
if (Arrays.asList(featuresNexus).contains(name)) return true;
|
||||
}
|
||||
if (name != null && Arrays.asList(featuresTensor).contains(name)
|
||||
&& !Arrays.asList(pTensorCodenames).contains(SystemProperties.get("ro.product.device"))) {
|
||||
return false;
|
||||
}
|
||||
if (Arrays.asList(featuresPixel).contains(name)) return true;
|
||||
if (Arrays.asList(featuresPixelOthers).contains(name)) return true;
|
||||
return mHasSystemFeatureCache.query(new HasSystemFeatureQuery(name, version));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,6 +75,8 @@ import java.util.Objects;
|
|||
import java.util.StringJoiner;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import com.android.internal.util.crdroid.PixelPropsUtils;
|
||||
|
||||
/**
|
||||
* Base class for implementing application instrumentation code. When running
|
||||
* with instrumentation turned on, this class will be instantiated for you
|
||||
|
|
@ -1356,6 +1358,7 @@ public class Instrumentation {
|
|||
Application app = getFactory(context.getPackageName())
|
||||
.instantiateApplication(cl, className);
|
||||
app.attach(context);
|
||||
PixelPropsUtils.setProps(context);
|
||||
return app;
|
||||
}
|
||||
|
||||
|
|
@ -1373,6 +1376,7 @@ public class Instrumentation {
|
|||
ClassNotFoundException {
|
||||
Application app = (Application)clazz.newInstance();
|
||||
app.attach(context);
|
||||
PixelPropsUtils.setProps(context);
|
||||
return app;
|
||||
}
|
||||
|
||||
|
|
|
|||
470
core/java/com/android/internal/util/crdroid/PixelPropsUtils.java
Normal file
470
core/java/com/android/internal/util/crdroid/PixelPropsUtils.java
Normal file
|
|
@ -0,0 +1,470 @@
|
|||
/*
|
||||
* Copyright (C) 2020 The Pixel Experience Project
|
||||
* 2021-2025 crDroid Android 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
|
||||
*
|
||||
* 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.util.crdroid;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.SystemProperties;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.internal.R;
|
||||
|
||||
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.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public final class PixelPropsUtils {
|
||||
|
||||
private static final String TAG = PixelPropsUtils.class.getSimpleName();
|
||||
private static final String DEVICE = "ro.product.device";
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
private static final String DATA_FILE = "gms_certified_props.json";
|
||||
|
||||
private static final String SPOOF_PIXEL_PI = "persist.sys.pixelprops.pi";
|
||||
private static final String SPOOF_PIXEL_GAMES = "persist.sys.pixelprops.games";
|
||||
private static final String SPOOF_PIXEL_GPHOTOS = "persist.sys.pixelprops.gphotos";
|
||||
private static final String SPOOF_PIXEL_NETFLIX = "persist.sys.pixelprops.netflix";
|
||||
|
||||
private static final Map<String, Object> propsToChangeGeneric;
|
||||
private static final Map<String, Object> propsToChangePixel9ProXL;
|
||||
private static final Map<String, Object> propsToChangePixelTablet;
|
||||
private static final Map<String, Object> propsToChangePixelXL;
|
||||
private static final Map<String, Object> propsToChangeROG6;
|
||||
private static final Map<String, Object> propsToChangeLenovoY700;
|
||||
private static final Map<String, Object> propsToChangeOP8P;
|
||||
private static final Map<String, Object> propsToChangeOP9P;
|
||||
private static final Map<String, Object> propsToChangeMI11TP;
|
||||
private static final Map<String, Object> propsToChangeMI13P;
|
||||
private static final Map<String, Object> propsToChangeF5;
|
||||
private static final Map<String, Object> propsToChangeBS4;
|
||||
private static final Map<String, Object> propsToChangeS24U;
|
||||
|
||||
// Packages to Spoof as the most recent Pixel device
|
||||
private static final String[] packagesToChangeRecentPixel = {
|
||||
"com.android.vending",
|
||||
"com.google.android.aicore",
|
||||
"com.google.android.apps.aiwallpapers",
|
||||
"com.google.android.apps.bard",
|
||||
"com.google.android.apps.customization.pixel",
|
||||
"com.google.android.apps.emojiwallpaper",
|
||||
"com.google.android.apps.nexuslauncher",
|
||||
"com.google.android.apps.photos",
|
||||
"com.google.android.apps.pixel.agent",
|
||||
"com.google.android.apps.pixel.creativeassistant",
|
||||
"com.google.android.apps.pixel.support",
|
||||
"com.google.android.apps.privacy.wildlife",
|
||||
"com.google.android.apps.wallpaper",
|
||||
"com.google.android.apps.wallpaper.pixel",
|
||||
"com.google.android.apps.weather",
|
||||
"com.google.android.gms",
|
||||
"com.google.android.googlequicksearchbox",
|
||||
"com.google.android.settings.intelligence",
|
||||
"com.google.android.wallpaper.effects",
|
||||
"com.google.pixel.livewallpaper",
|
||||
"com.netflix.mediaclient",
|
||||
"com.nhs.online.nhsonline"
|
||||
};
|
||||
|
||||
// Packages to Spoof as ROG Phone 6
|
||||
private static final String[] packagesToChangeROG6 = {
|
||||
"com.ea.gp.fifamobile",
|
||||
"com.gameloft.android.ANMP.GloftA9HM",
|
||||
"com.madfingergames.legends",
|
||||
"com.pearlabyss.blackdesertm",
|
||||
"com.pearlabyss.blackdesertm.gl"
|
||||
};
|
||||
|
||||
// Packages to Spoof as Lenovo Y700
|
||||
private static final String[] packagesToChangeLenovoY700 = {
|
||||
"com.activision.callofduty.shooter",
|
||||
"com.garena.game.codm",
|
||||
"com.tencent.tmgp.kr.codm",
|
||||
"com.vng.codmvn"
|
||||
};
|
||||
|
||||
// Packages to Spoof as OnePlus 8 Pro
|
||||
private static final String[] packagesToChangeOP8P = {
|
||||
"com.netease.lztgglobal",
|
||||
"com.riotgames.league.wildrift",
|
||||
"com.riotgames.league.wildrifttw",
|
||||
"com.riotgames.league.wildriftvn",
|
||||
"com.riotgames.league.teamfighttactics",
|
||||
"com.riotgames.league.teamfighttacticstw",
|
||||
"com.riotgames.league.teamfighttacticsvn"
|
||||
};
|
||||
|
||||
// Packages to Spoof as OnePlus 9 Pro
|
||||
private static final String[] packagesToChangeOP9P = {
|
||||
"com.epicgames.fortnite",
|
||||
"com.epicgames.portal",
|
||||
"com.tencent.lolm"
|
||||
};
|
||||
|
||||
// Packages to Spoof as Mi 11T Pro
|
||||
private static final String[] packagesToChangeMI11TP = {
|
||||
"com.ea.gp.apexlegendsmobilefps",
|
||||
"com.levelinfinite.hotta.gp",
|
||||
"com.supercell.clashofclans",
|
||||
"com.vng.mlbbvn"
|
||||
};
|
||||
|
||||
// Packages to Spoof as Xiaomi 13 Pro
|
||||
private static final String[] packagesToChangeMI13P = {
|
||||
"com.levelinfinite.sgameGlobal",
|
||||
"com.tencent.tmgp.sgame"
|
||||
};
|
||||
|
||||
// Packages to Spoof as POCO F5
|
||||
private static final String[] packagesToChangeF5 = {
|
||||
"com.dts.freefiremax",
|
||||
"com.dts.freefireth",
|
||||
"com.mobile.legends"
|
||||
};
|
||||
|
||||
// Packages to Spoof as Black Shark 4
|
||||
private static final String[] packagesToChangeBS4 = {
|
||||
"com.proximabeta.mf.uamo"
|
||||
};
|
||||
|
||||
// Packages to Spoof as Samsung S24 Ultra
|
||||
private static final String[] packagesToChangeS24U = {
|
||||
"com.pubg.imobile",
|
||||
"com.pubg.krmobile",
|
||||
"com.rekoo.pubgm",
|
||||
"com.tencent.ig",
|
||||
"com.tencent.tmgp.pubgmhd",
|
||||
"com.vng.pubgmobile"
|
||||
};
|
||||
|
||||
private static volatile boolean sIsFinsky = false;
|
||||
private static volatile List<String> sCertifiedProps = new ArrayList<>();
|
||||
|
||||
static {
|
||||
propsToChangeGeneric = new HashMap<>();
|
||||
propsToChangeGeneric.put("TYPE", "user");
|
||||
propsToChangeGeneric.put("TAGS", "release-keys");
|
||||
propsToChangePixel9ProXL = new HashMap<>();
|
||||
propsToChangePixel9ProXL.put("BRAND", "google");
|
||||
propsToChangePixel9ProXL.put("MANUFACTURER", "Google");
|
||||
propsToChangePixel9ProXL.put("DEVICE", "komodo");
|
||||
propsToChangePixel9ProXL.put("PRODUCT", "komodo");
|
||||
propsToChangePixel9ProXL.put("HARDWARE", "komodo");
|
||||
propsToChangePixel9ProXL.put("MODEL", "Pixel 9 Pro XL");
|
||||
propsToChangePixel9ProXL.put("ID", "BP1A.250405.007");
|
||||
propsToChangePixel9ProXL.put("FINGERPRINT", "google/komodo/komodo:15/BP1A.250405.007/13240079:user/release-keys");
|
||||
propsToChangePixelTablet = new HashMap<>();
|
||||
propsToChangePixelTablet.put("BRAND", "google");
|
||||
propsToChangePixelTablet.put("MANUFACTURER", "Google");
|
||||
propsToChangePixelTablet.put("DEVICE", "tangorpro");
|
||||
propsToChangePixelTablet.put("PRODUCT", "tangorpro");
|
||||
propsToChangePixelTablet.put("HARDWARE", "tangorpro");
|
||||
propsToChangePixelTablet.put("MODEL", "Pixel Tablet");
|
||||
propsToChangePixelTablet.put("ID", "BP1A.250405.007");
|
||||
propsToChangePixelTablet.put("FINGERPRINT", "google/tangorpro/tangorpro:15/BP1A.250405.007/13240079:user/release-keys");
|
||||
propsToChangePixelXL = new HashMap<>();
|
||||
propsToChangePixelXL.put("BRAND", "google");
|
||||
propsToChangePixelXL.put("MANUFACTURER", "Google");
|
||||
propsToChangePixelXL.put("DEVICE", "marlin");
|
||||
propsToChangePixelXL.put("PRODUCT", "marlin");
|
||||
propsToChangePixelXL.put("HARDWARE", "marlin");
|
||||
propsToChangePixelXL.put("MODEL", "Pixel XL");
|
||||
propsToChangePixelXL.put("ID", "QP1A.191005.007.A3");
|
||||
propsToChangePixelXL.put("FINGERPRINT", "google/marlin/marlin:10/QP1A.191005.007.A3/5972272:user/release-keys");
|
||||
propsToChangeROG6 = new HashMap<>();
|
||||
propsToChangeROG6.put("BRAND", "asus");
|
||||
propsToChangeROG6.put("MANUFACTURER", "asus");
|
||||
propsToChangeROG6.put("DEVICE", "AI2201");
|
||||
propsToChangeROG6.put("MODEL", "ASUS_AI2201");
|
||||
propsToChangeLenovoY700 = new HashMap<>();
|
||||
propsToChangeLenovoY700.put("MODEL", "Lenovo TB-9707F");
|
||||
propsToChangeLenovoY700.put("MANUFACTURER", "lenovo");
|
||||
propsToChangeOP8P = new HashMap<>();
|
||||
propsToChangeOP8P.put("MODEL", "IN2020");
|
||||
propsToChangeOP8P.put("MANUFACTURER", "OnePlus");
|
||||
propsToChangeOP9P = new HashMap<>();
|
||||
propsToChangeOP9P.put("MODEL", "LE2123");
|
||||
propsToChangeOP9P.put("MANUFACTURER", "OnePlus");
|
||||
propsToChangeMI11TP = new HashMap<>();
|
||||
propsToChangeMI11TP.put("MODEL", "2107113SI");
|
||||
propsToChangeMI11TP.put("MANUFACTURER", "Xiaomi");
|
||||
propsToChangeMI13P = new HashMap<>();
|
||||
propsToChangeMI13P.put("BRAND", "Xiaomi");
|
||||
propsToChangeMI13P.put("MANUFACTURER", "Xiaomi");
|
||||
propsToChangeMI13P.put("MODEL", "2210132C");
|
||||
propsToChangeF5 = new HashMap<>();
|
||||
propsToChangeF5.put("MODEL", "23049PCD8G");
|
||||
propsToChangeF5.put("MANUFACTURER", "Xiaomi");
|
||||
propsToChangeBS4 = new HashMap<>();
|
||||
propsToChangeBS4.put("MODEL", "2SM-X706B");
|
||||
propsToChangeBS4.put("MANUFACTURER", "blackshark");
|
||||
propsToChangeS24U = new HashMap<>();
|
||||
propsToChangeS24U.put("BRAND", "SAMSUNG");
|
||||
propsToChangeS24U.put("DEVICE", "S24 ULTRA");
|
||||
propsToChangeS24U.put("MANUFACTURER", "SM-S928B");
|
||||
propsToChangeS24U.put("MODEL", "SM-S928B");
|
||||
|
||||
}
|
||||
|
||||
public static void setProps(Context context) {
|
||||
final String packageName = context.getPackageName();
|
||||
if (packageName == null || packageName.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
propsToChangeGeneric.forEach((k, v) -> setPropValue(k, v));
|
||||
|
||||
if (Arrays.asList(packagesToChangeRecentPixel).contains(packageName)) {
|
||||
|
||||
Map<String, Object> propsToChange = new HashMap<>();
|
||||
|
||||
if (packageName.equals("com.google.android.apps.photos")) {
|
||||
if (SystemProperties.getBoolean(SPOOF_PIXEL_GPHOTOS, true)) {
|
||||
propsToChange.putAll(propsToChangePixelXL);
|
||||
}
|
||||
} else if (packageName.equals("com.netflix.mediaclient") &&
|
||||
!SystemProperties.getBoolean(SPOOF_PIXEL_NETFLIX, false)) {
|
||||
if (DEBUG) Log.d(TAG, "Netflix spoofing disabled by system prop");
|
||||
return;
|
||||
} else if (packageName.equals("com.android.vending")) {
|
||||
sIsFinsky = true;
|
||||
return;
|
||||
} else if (packageName.equals("com.google.android.gms")) {
|
||||
final String processName = Application.getProcessName().toLowerCase();
|
||||
if (processName.contains("unstable")) {
|
||||
spoofBuildGms(context);
|
||||
return;
|
||||
}
|
||||
return;
|
||||
} else if (packageName.equals("com.google.android.settings.intelligence")) {
|
||||
setPropValue("FINGERPRINT", Build.VERSION.INCREMENTAL);
|
||||
return;
|
||||
} else {
|
||||
if (isDeviceTablet(context.getApplicationContext())) {
|
||||
propsToChange.putAll(propsToChangePixelTablet);
|
||||
} else {
|
||||
propsToChange.putAll(propsToChangePixel9ProXL);
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG) Log.d(TAG, "Defining props for: " + packageName);
|
||||
for (Map.Entry<String, Object> prop : propsToChange.entrySet()) {
|
||||
String key = prop.getKey();
|
||||
Object value = prop.getValue();
|
||||
if (DEBUG) Log.d(TAG, "Defining " + key + " prop for: " + packageName);
|
||||
setPropValue(key, value);
|
||||
}
|
||||
} else {
|
||||
|
||||
if (!SystemProperties.getBoolean(SPOOF_PIXEL_GAMES, false))
|
||||
return;
|
||||
|
||||
if (Arrays.asList(packagesToChangeROG6).contains(packageName)) {
|
||||
if (DEBUG) Log.d(TAG, "Defining props for: " + packageName);
|
||||
for (Map.Entry<String, Object> prop : propsToChangeROG6.entrySet()) {
|
||||
String key = prop.getKey();
|
||||
Object value = prop.getValue();
|
||||
setPropValue(key, value);
|
||||
}
|
||||
} else if (Arrays.asList(packagesToChangeLenovoY700).contains(packageName)) {
|
||||
if (DEBUG) Log.d(TAG, "Defining props for: " + packageName);
|
||||
for (Map.Entry<String, Object> prop : propsToChangeLenovoY700.entrySet()) {
|
||||
String key = prop.getKey();
|
||||
Object value = prop.getValue();
|
||||
setPropValue(key, value);
|
||||
}
|
||||
} else if (Arrays.asList(packagesToChangeOP8P).contains(packageName)) {
|
||||
if (DEBUG) Log.d(TAG, "Defining props for: " + packageName);
|
||||
for (Map.Entry<String, Object> prop : propsToChangeOP8P.entrySet()) {
|
||||
String key = prop.getKey();
|
||||
Object value = prop.getValue();
|
||||
setPropValue(key, value);
|
||||
}
|
||||
} else if (Arrays.asList(packagesToChangeOP9P).contains(packageName)) {
|
||||
if (DEBUG) Log.d(TAG, "Defining props for: " + packageName);
|
||||
for (Map.Entry<String, Object> prop : propsToChangeOP9P.entrySet()) {
|
||||
String key = prop.getKey();
|
||||
Object value = prop.getValue();
|
||||
setPropValue(key, value);
|
||||
}
|
||||
} else if (Arrays.asList(packagesToChangeMI11TP).contains(packageName)) {
|
||||
if (DEBUG) Log.d(TAG, "Defining props for: " + packageName);
|
||||
for (Map.Entry<String, Object> prop : propsToChangeMI11TP.entrySet()) {
|
||||
String key = prop.getKey();
|
||||
Object value = prop.getValue();
|
||||
setPropValue(key, value);
|
||||
}
|
||||
} else if (Arrays.asList(packagesToChangeMI13P).contains(packageName)) {
|
||||
if (DEBUG) Log.d(TAG, "Defining props for: " + packageName);
|
||||
for (Map.Entry<String, Object> prop : propsToChangeMI13P.entrySet()) {
|
||||
String key = prop.getKey();
|
||||
Object value = prop.getValue();
|
||||
setPropValue(key, value);
|
||||
}
|
||||
} else if (Arrays.asList(packagesToChangeF5).contains(packageName)) {
|
||||
if (DEBUG) Log.d(TAG, "Defining props for: " + packageName);
|
||||
for (Map.Entry<String, Object> prop : propsToChangeF5.entrySet()) {
|
||||
String key = prop.getKey();
|
||||
Object value = prop.getValue();
|
||||
setPropValue(key, value);
|
||||
}
|
||||
} else if (Arrays.asList(packagesToChangeBS4).contains(packageName)) {
|
||||
if (DEBUG) Log.d(TAG, "Defining props for: " + packageName);
|
||||
for (Map.Entry<String, Object> prop : propsToChangeBS4.entrySet()) {
|
||||
String key = prop.getKey();
|
||||
Object value = prop.getValue();
|
||||
setPropValue(key, value);
|
||||
}
|
||||
} else if (Arrays.asList(packagesToChangeS24U).contains(packageName)) {
|
||||
if (DEBUG) Log.d(TAG, "Defining props for: " + packageName);
|
||||
for (Map.Entry<String, Object> prop : propsToChangeS24U.entrySet()) {
|
||||
String key = prop.getKey();
|
||||
Object value = prop.getValue();
|
||||
setPropValue(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDeviceTablet(Context context) {
|
||||
if (context == null) {
|
||||
return false;
|
||||
}
|
||||
Configuration config = context.getResources().getConfiguration();
|
||||
boolean isTablet = (config.smallestScreenWidthDp >= 600);
|
||||
return isTablet;
|
||||
}
|
||||
|
||||
private static void setPropValue(String key, Object value) {
|
||||
setPropValue(key, value.toString());
|
||||
}
|
||||
|
||||
private static void setPropValue(String key, String value) {
|
||||
try {
|
||||
if (DEBUG) Log.d(TAG, "Defining prop " + key + " to " + value);
|
||||
Class<?> clazz = Build.class;
|
||||
if (key.startsWith("VERSION.")) {
|
||||
clazz = Build.VERSION.class;
|
||||
key = key.substring(8);
|
||||
}
|
||||
Field field = clazz.getDeclaredField(key);
|
||||
field.setAccessible(true);
|
||||
// Determine the field type and parse the value accordingly.
|
||||
if (field.getType().equals(Integer.TYPE)) {
|
||||
field.set(null, Integer.parseInt(value));
|
||||
} else if (field.getType().equals(Long.TYPE)) {
|
||||
field.set(null, Long.parseLong(value));
|
||||
} else {
|
||||
field.set(null, value);
|
||||
}
|
||||
field.setAccessible(false);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to set prop " + key, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void spoofBuildGms(Context context) {
|
||||
if (!SystemProperties.getBoolean(SPOOF_PIXEL_PI, true))
|
||||
return;
|
||||
|
||||
File dataFile = new File(Environment.getDataSystemDirectory(), DATA_FILE);
|
||||
String savedProps = readFromFile(dataFile);
|
||||
|
||||
if (TextUtils.isEmpty(savedProps)) {
|
||||
Log.d(TAG, "Parsing props locally - data file unavailable");
|
||||
sCertifiedProps = Arrays.asList(context.getResources().getStringArray(R.array.config_certifiedBuildProperties));
|
||||
} else {
|
||||
Log.d(TAG, "Parsing props fetched by attestation service");
|
||||
try {
|
||||
JSONObject parsedProps = new JSONObject(savedProps);
|
||||
Iterator<String> keys = parsedProps.keys();
|
||||
|
||||
while (keys.hasNext()) {
|
||||
String key = keys.next();
|
||||
String value = parsedProps.getString(key);
|
||||
sCertifiedProps.add(key + ":" + value);
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "Error parsing JSON data", e);
|
||||
Log.d(TAG, "Parsing props locally as fallback");
|
||||
sCertifiedProps = Arrays.asList(context.getResources().getStringArray(R.array.config_certifiedBuildProperties));
|
||||
}
|
||||
}
|
||||
|
||||
// Alter build parameters to avoid hardware attestation enforcement
|
||||
for (String entry : sCertifiedProps) {
|
||||
// Each entry must be of the format FIELD:value
|
||||
final String[] fieldAndProp = entry.split(":", 2);
|
||||
if (fieldAndProp.length != 2) {
|
||||
Log.e(TAG, "Invalid entry in certified props: " + entry);
|
||||
continue;
|
||||
}
|
||||
setPropValue(fieldAndProp[0], fieldAndProp[1]);
|
||||
}
|
||||
}
|
||||
|
||||
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 boolean isCallerSafetyNet() {
|
||||
return Arrays.stream(Thread.currentThread().getStackTrace())
|
||||
.anyMatch(elem -> elem.getClassName().toLowerCase()
|
||||
.contains("droidguard"));
|
||||
}
|
||||
|
||||
public static void onEngineGetCertificateChain() {
|
||||
if (!SystemProperties.getBoolean(SPOOF_PIXEL_PI, true))
|
||||
return;
|
||||
// Check stack for SafetyNet or Play Integrity
|
||||
if (isCallerSafetyNet() || sIsFinsky) {
|
||||
Log.i(TAG, "Blocked key attestation");
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
21
core/res/res/values/scoop_config.xml
Normal file
21
core/res/res/values/scoop_config.xml
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2016-2025 crDroid Android Project
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<!-- Build properties from a GMS certified device against current platform SPL level -->
|
||||
<string-array name="config_certifiedBuildProperties" translatable="false">
|
||||
<!--
|
||||
Each entry must be of the format
|
||||
FIELD:value
|
||||
with the field belonging to android.os.Build or android.os.Build.VERSION class.
|
||||
Example:
|
||||
<item>BRAND:foo</item>
|
||||
<item>DEVICE:bar</item>
|
||||
<item>FINGERPRINT:foo/bar/bar:1.0/lorem/ipsum:dolor/sit-amet</item>
|
||||
<item>VERSION.RELEASE:1.0</item>
|
||||
-->
|
||||
</string-array>
|
||||
</resources>
|
||||
10
core/res/res/values/scoop_symbols.xml
Normal file
10
core/res/res/values/scoop_symbols.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2016-2025 crDroid Android Project
|
||||
SPDX-License-Identifier: Apache-2.0
|
||||
-->
|
||||
<resources>
|
||||
|
||||
<!-- Build properties from a GMS certified device against current platform SPL level -->
|
||||
<java-symbol type="array" name="config_certifiedBuildProperties" />
|
||||
</resources>
|
||||
|
|
@ -90,6 +90,8 @@ import java.util.NoSuchElementException;
|
|||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import com.android.internal.util.crdroid.PixelPropsUtils;
|
||||
|
||||
/**
|
||||
* A java.security.KeyStore interface for the Android KeyStore. An instance of
|
||||
* it can be created via the {@link java.security.KeyStore#getInstance(String)
|
||||
|
|
@ -178,6 +180,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi {
|
|||
|
||||
@Override
|
||||
public Certificate[] engineGetCertificateChain(String alias) {
|
||||
PixelPropsUtils.onEngineGetCertificateChain();
|
||||
|
||||
KeyEntryResponse response = getKeyMetadata(alias);
|
||||
|
||||
if (response == null || response.metadata.certificate == null) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,192 @@
|
|||
/*
|
||||
* Copyright (C) 2024 The LeafOS Project
|
||||
* Copyright (C) 2024 crDroid Android Project
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
*/
|
||||
|
||||
package com.android.server.crdroid;
|
||||
|
||||
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 com.android.internal.util.crdroid.Utils;
|
||||
|
||||
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://raw.githubusercontent.com/crdroidandroid/android_vendor_certification/refs/heads/15.0/gms_certified_props.json";
|
||||
private static final String SPOOF_PIXEL_PI = "persist.sys.pixelprops.pi";
|
||||
private static final String DATA_FILE = "gms_certified_props.json";
|
||||
private static final long INITIAL_DELAY = 0; // Start immediately on boot
|
||||
private static final long INTERVAL = 8; // Interval in hours
|
||||
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
|
||||
|
||||
private final Context mContext;
|
||||
private final File mDataFile;
|
||||
private final ScheduledExecutorService mScheduler;
|
||||
private final ConnectivityManager mConnectivityManager;
|
||||
private final FetchGmsCertifiedProps mFetchRunnable;
|
||||
|
||||
private boolean mPendingUpdate;
|
||||
|
||||
public AttestationService(Context context) {
|
||||
super(context);
|
||||
mContext = context;
|
||||
mDataFile = new File(Environment.getDataSystemDirectory(), DATA_FILE);
|
||||
mFetchRunnable = new FetchGmsCertifiedProps();
|
||||
mScheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
mConnectivityManager =
|
||||
(ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
registerNetworkCallback();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {}
|
||||
|
||||
@Override
|
||||
public void onBootPhase(int phase) {
|
||||
if (Utils.isPackageInstalled(mContext, "com.google.android.gms")
|
||||
&& phase == PHASE_BOOT_COMPLETED) {
|
||||
Log.i(TAG, "Scheduling the service");
|
||||
mScheduler.scheduleAtFixedRate(
|
||||
mFetchRunnable, INITIAL_DELAY, INTERVAL, TimeUnit.HOURS);
|
||||
}
|
||||
}
|
||||
|
||||
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 void dlog(String message) {
|
||||
if (DEBUG) Log.d(TAG, message);
|
||||
}
|
||||
|
||||
private boolean isInternetConnected() {
|
||||
Network network = mConnectivityManager.getActiveNetwork();
|
||||
if (network != null) {
|
||||
NetworkCapabilities capabilities = mConnectivityManager.getNetworkCapabilities(network);
|
||||
return capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void registerNetworkCallback() {
|
||||
mConnectivityManager.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback() {
|
||||
@Override
|
||||
public void onAvailable(Network network) {
|
||||
Log.i(TAG, "Internet is available, resuming update");
|
||||
if (mPendingUpdate) {
|
||||
mScheduler.schedule(mFetchRunnable, 0, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class FetchGmsCertifiedProps implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
if (!SystemProperties.getBoolean(SPOOF_PIXEL_PI, true)) {
|
||||
mPendingUpdate = false;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
dlog("FetchGmsCertifiedProps started");
|
||||
|
||||
if (!isInternetConnected()) {
|
||||
Log.e(TAG, "Internet is unavailable, deferring update");
|
||||
mPendingUpdate = true;
|
||||
return;
|
||||
}
|
||||
mPendingUpdate = false;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -163,6 +163,7 @@ import com.android.server.contextualsearch.ContextualSearchManagerService;
|
|||
import com.android.server.coverage.CoverageService;
|
||||
import com.android.server.cpu.CpuMonitorService;
|
||||
import com.android.server.crashrecovery.CrashRecoveryAdaptor;
|
||||
import com.android.server.crdroid.AttestationService;
|
||||
import com.android.server.credentials.CredentialManagerService;
|
||||
import com.android.server.criticalevents.CriticalEventLog;
|
||||
import com.android.server.devicepolicy.DevicePolicyManagerService;
|
||||
|
|
@ -2757,6 +2758,11 @@ public final class SystemServer implements Dumpable {
|
|||
mSystemServiceManager.startService(BackgroundInstallControlService.class);
|
||||
t.traceEnd();
|
||||
}
|
||||
|
||||
// AttestationService
|
||||
t.traceBegin("AttestationService");
|
||||
mSystemServiceManager.startService(AttestationService.class);
|
||||
t.traceEnd();
|
||||
}
|
||||
|
||||
t.traceBegin("StartMediaProjectionManager");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue