feat(app): 优化应用禁用配置写入并屏蔽应用库管理应用
This commit is contained in:
1 parent
82db4c359c
commit
438d92963f
5 files changed
+236
-7
No files matched your search
+2
-2
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user