feat(app): 优化应用禁用配置写入并屏蔽应用库管理应用

This commit is contained in:
tongtong committed 2026-10-27 18:15:40 +08:00
1 parent 82db4c359c
commit 438d92963f
5 files changed
+236 -7

No files matched your search

+2 -2
View File
@@ -18,8 +18,8 @@ android {
defaultConfig {
applicationId "com.aoleyun.sn"
versionCode 256
versionName "1.6.1026"
versionCode 257
versionName "1.6.1027"
//There are no CERT files because If the mini sdk version is 23+, the AGP will ignore the V1 scheme signature.
minSdkVersion 24
@@ -3240,7 +3240,8 @@ public class NetInterfaceManager {
return appDateInfo.getApp_baoming();
}
}).collect(Collectors.toSet());
appNames.addAll(pkgs);
// TODO: 2026/10/27 后台 -应用上架 --应用库管理 页面上传的应用,先屏蔽
// appNames.addAll(pkgs);
}
if (rankAppResponse.isSuccess()) {
List<AppDateInfo> appDateInfos = rankAppResponse.data;
@@ -3290,7 +3291,6 @@ public class NetInterfaceManager {
} else {
mMMKV.encode(CommonConfig.AOLE_ALL_APP_WHITE_LIST_KEY, new HashSet<>(strings));
JgyUtils.getInstance().writeAppPackageList(strings);
JgyUtils.getInstance().deleteOtherApp();
}
}
@@ -3303,6 +3303,7 @@ public class NetInterfaceManager {
@Override
public void onComplete() {
Log.e("getAppLimitList", "onComplete: ");
JgyUtils.getInstance().deleteOtherApp();
if (callback != null)
callback.onComplete();
}
@@ -56,7 +56,7 @@ public class UrlAddress {
public static final String SET_WHITE_PACKAGE_LIST = "firmware/index";
/*获取全部应用*/
public static final String GET_ALL_APP = "recommend/index";
/*应用管控的应用*/
/*应用库管理的应用*/
public static final String GET_ADMIN_APP = "And/app/getAdminApp";
/*公共应用*/
public static final String HTTP_TAG_COMMOAPP = "Rank/commonApp";
@@ -554,6 +554,12 @@ public class MainService extends BaseService implements NetworkUtils.OnNetworkSt
if (TextUtils.isEmpty(allowAppList)) {
Settings.System.putString(getContentResolver(), CommonConfig.SEEWO_APP_SOURCE_INSTALL_WHITELIST, String.join(",", JgyUtils.DEFAULT_APP_SOURCE_WHITE_LIST));
}
String aole_app_forbid = Settings.System.getString(getContentResolver(), CommonConfig.AOLE_ACTION_APP_FORBID);
if (TextUtils.isEmpty(aole_app_forbid)) {
JgyUtils.getInstance().writeAppPackageList();
}
} catch (Exception e) {
Log.e(TAG, "initOnBackground: " + e.getMessage());
e.printStackTrace();
@@ -28,6 +28,7 @@ import android.net.NetworkInfo;
import android.net.Uri;
import android.os.BatteryManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Debug;
import android.os.Environment;
import android.os.Handler;
@@ -1642,7 +1643,7 @@ public class JgyUtils {
String aole_app_forbid = String.join(",", pkgSet);
Log.e(TAG, "writeAppPackageList: " + aole_app_forbid);
try {
boolean b = Settings.System.putString(crv, CommonConfig.AOLE_ACTION_APP_FORBID, aole_app_forbid);
boolean b = androidSettingsPutString(mContext, CommonConfig.AOLE_ACTION_APP_FORBID, aole_app_forbid);
Log.e("writeAppPackageList: ", "aole_app_forbid: " + b + " " + Settings.System.getString(crv, CommonConfig.AOLE_ACTION_APP_FORBID));
} catch (Exception e) {
Log.e(TAG, "writeAppPackageList: Settings.System.putString failed", e);
@@ -1672,12 +1673,233 @@ public class JgyUtils {
String aole_app_forbid = String.join(",", pkgSet);
Log.e(TAG, "writeAppPackageList: " + aole_app_forbid);
try {
Settings.System.putString(crv, CommonConfig.AOLE_ACTION_APP_FORBID, aole_app_forbid);
androidSettingsPutString(mContext, CommonConfig.AOLE_ACTION_APP_FORBID, aole_app_forbid);
} catch (Exception e) {
Log.e(TAG, "writeAppPackageList: Settings.System.putString failed", e);
}
}
private static final String CALL_METHOD_PUT_SYSTEM = "PUT_system";
private static final String CALL_METHOD_USER_KEY = "_user";
private static final String SETTINGS_AUTHORITY = "settings";
private static final Uri SETTINGS_URI = Uri.parse("content://" + SETTINGS_AUTHORITY);
private static final String SETTINGS_PROVIDER_NAME =
"com.android.providers.settings/.SettingsProvider";
// settings provider 的 IContentProvider 代理缓存;binder 死亡后自动重取
private static volatile Object sSettingsProvider;
public static boolean androidSettingsPutString(Context context, String name, String value) {
Context targetContext = context;
if (targetContext == null && sInstance != null) {
targetContext = sInstance.mContext;
}
if (targetContext == null) {
Log.e(TAG, "androidSettingsPutString: Context is null");
return false;
}
if (TextUtils.isEmpty(name)) {
Log.e(TAG, "androidSettingsPutString: name is empty");
return false;
}
Bundle arg = new Bundle();
arg.putString(Settings.NameValueTable.VALUE, value);
// _user 是 SettingsProvider 定位落库用户的必填项,与包名伪装无关;只管主用户
arg.putInt(CALL_METHOD_USER_KEY, 0);
// 注意:发起方进程绝不能 clearCallingIdentity——它会篡改本进程事务携带的
// 调用身份,SettingsProvider 侧校验 calling pid 会直接拒绝("doesn't match source pid")。
// 那个手法只适用于服务端(代理进程处理入站 binder 调用时)。
Bundle result = callSettingsPut(targetContext, arg, name);
boolean bundleOk = isOk(result);
// 该厂商 SettingsProvider 对 PUT_system 静默返回 null(不抛异常也不回 Bundle),
// 成败以写后读回为准。
String readBack = null;
try {
readBack = Settings.System.getString(targetContext.getContentResolver(), name);
} catch (Throwable t) {
Log.w(TAG, "androidSettingsPutString: read back failed for " + name + ": " + t);
}
boolean verified = TextUtils.equals(readBack, value);
if (!bundleOk && !verified) {
Log.w(TAG, "androidSettingsPutString: failed for " + name
+ " (result=" + result + ", readBack=" + readBack + ")");
return false;
}
Log.d(TAG, "androidSettingsPutString succeeded for " + name
+ " (bundleOk=" + bundleOk + ", verified=" + verified + ")");
return true;
}
/**
* 直路一条:反射 acquireExistingProvider 拿 ContentProviderProxy,
* 用伪造的 AttributionSource(uid=1000, package="android") 调其 call。
* 事务由设备框架自己生成的 Proxy 写出,事务码/布局永远和 ROM 匹配。
* 前提:android.uid.system + platform 签名。
*/
private static Bundle callSettingsPut(Context context, Bundle arg, String name) {
Object provider = getSettingsProvider(context);
if (provider == null) {
return null;
}
Bundle result = invokeProviderCall(provider, arg, name);
if (result == null) {
// 可能 binder 已死亡,清缓存重取再试一次
sSettingsProvider = null;
provider = getSettingsProvider(context);
if (provider != null) {
result = invokeProviderCall(provider, arg, name);
}
}
return result;
}
/**
* S+ 的 ContentProviderProxy 记账包名取自 AttributionSource:
* 伪造 AttributionSource(uid=1000, packageName="android") 调
* call(AttributionSource, authority, method, arg, extras)。
* 本 App 就是 uid 1000,enforceCallingUid 校验通过;"android" 包属于 uid 1000,
* attribution 链校验也通过。旧 4 参 call 无包名槽位(会记真实包名),不用。
*/
private static Bundle invokeProviderCall(Object provider, Bundle arg, String name) {
Object attribution = buildAndroidAttributionSource();
if (attribution == null) {
Log.e(TAG, "androidSettingsPutString: attribution source is null, abort");
return null;
}
for (Method m : provider.getClass().getMethods()) {
if (!"call".equals(m.getName())) {
continue;
}
Class<?>[] p = m.getParameterTypes();
if (p.length == 5 && p[1] == String.class && p[4] == Bundle.class
&& p[0].getName().equals("android.content.AttributionSource")) {
try {
Bundle r = (Bundle) m.invoke(provider, attribution, SETTINGS_AUTHORITY,
CALL_METHOD_PUT_SYSTEM, name, arg);
Log.d(TAG, "androidSettingsPutString: invoked call"
+ java.util.Arrays.toString(p) + " -> "
+ (r == null ? "null" : r.toString()));
return r;
} catch (Throwable t) {
Throwable root = t instanceof java.lang.reflect.InvocationTargetException
&& t.getCause() != null ? t.getCause() : t;
Log.w(TAG, "androidSettingsPutString: provider call failed: " + root);
return null;
}
}
}
Log.w(TAG, "androidSettingsPutString: no AttributionSource call overload on "
+ provider.getClass().getName());
return null;
}
/**
* AttributionSource.Builder(uid).setPackageName("android").build(),反射构造。
*/
private static Object buildAndroidAttributionSource() {
try {
Class<?> as = Class.forName("android.content.AttributionSource");
Class<?> builder = Class.forName("android.content.AttributionSource$Builder");
Object b = builder.getConstructor(int.class).newInstance(Process.myUid());
builder.getMethod("setPackageName", String.class).invoke(b, "android");
return builder.getMethod("build").invoke(b);
} catch (Throwable t) {
Log.e(TAG, "androidSettingsPutString: build AttributionSource failed: " + t);
return null;
}
}
private static boolean isOk(Bundle result) {
return result != null && !"error".equals(result.getString("tag"));
}
/**
* 反射 ContentResolver.acquireProvider / acquireExistingProvider(各 ROM 签名不同,
* 扫描方法名+按参数类型填槽,String 槽位两种顺序都试),返回并缓存 IContentProvider 代理。
* 调用者是 android.uid.system,hidden API 豁免生效。
*/
private static Object getSettingsProvider(Context context) {
Object cached = sSettingsProvider;
if (cached != null) {
return cached;
}
synchronized (JgyUtils.class) {
if (sSettingsProvider != null) {
return sSettingsProvider;
}
ContentResolver cr = context.getContentResolver();
java.util.List<Method> candidates = new java.util.ArrayList<>();
for (Method m : ContentResolver.class.getDeclaredMethods()) {
String n = m.getName();
if (("acquireProvider".equals(n) || "acquireExistingProvider".equals(n))
&& m.getReturnType().isInterface()) {
candidates.add(m);
}
}
java.util.Collections.sort(candidates,
(a, b) -> a.getParameterCount() - b.getParameterCount());
for (Method m : candidates) {
Object provider = tryAcquire(cr, m);
if (provider != null) {
sSettingsProvider = provider;
break;
}
}
if (sSettingsProvider == null) {
Log.e(TAG, "androidSettingsPutString: settings provider unavailable");
}
return sSettingsProvider;
}
}
/**
* 3 参版是 (authority, name, ...),5 参版是 (config, name, userId, authority, stable),
* String 槽位顺序在不同 API 上相反,猜错会 NPE,两组组合都试。
*/
private static Object tryAcquire(ContentResolver cr, Method m) {
Class<?>[] types = m.getParameterTypes();
String[][] slotCombos = {
{SETTINGS_AUTHORITY, SETTINGS_PROVIDER_NAME}, // authority 在前
{SETTINGS_PROVIDER_NAME, SETTINGS_AUTHORITY}, // name 在前
};
for (String[] combo : slotCombos) {
try {
Object[] args = new Object[types.length];
int stringSlot = 0;
for (int i = 0; i < types.length; i++) {
Class<?> t = types[i];
if (t == String.class) {
args[i] = stringSlot < combo.length ? combo[stringSlot] : null;
stringSlot++;
} else if (t == Uri.class) {
args[i] = SETTINGS_URI;
} else if (t == boolean.class) {
args[i] = false; // stable / tryLocal:走 binder 路径
} else if (t == int.class) {
args[i] = 0; // userId / pid(0 会让 ActivityThread 用 myPid)
}
// IBinder、Intent 等保持 null
}
m.setAccessible(true);
Object provider = m.invoke(cr, args);
if (provider != null) {
Log.d(TAG, "androidSettingsPutString: acquired via " + m.getName()
+ java.util.Arrays.toString(types) + " slots=" + combo[0] + "/" + combo[1]);
return provider;
}
} catch (Throwable t) {
Throwable root = t instanceof java.lang.reflect.InvocationTargetException
&& t.getCause() != null ? t.getCause() : t;
Log.w(TAG, "androidSettingsPutString: " + m.getName() + " slots=" + combo[0]
+ "/" + combo[1] + " failed: " + root);
}
}
return null;
}
@Deprecated
public void checkForceDownload() {
// String jsonString = cacheHelper.getAsString(UrlAddress.GET_FORCE_INSTALL_LIST);