This commit is contained in:
2025-08-17 20:17:54 +08:00
commit 282329c8bf
29 changed files with 728 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/build
+33
View File
@@ -0,0 +1,33 @@
plugins {
alias(libs.plugins.android.application)
}
android {
namespace = "top.littlew.acer"
compileSdk = 36
defaultConfig {
applicationId = "top.littlew.acer"
minSdk = 33
targetSdk = 36
versionCode = 1
versionName = "1.0"
}
buildTypes {
release {
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
dependencies {
compileOnly("de.robv.android.xposed:api:82")
}
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:icon="@mipmap/ic_launcher"
android:label="App Clone Enabler">
<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="Enable App Clone for all user apps." />
<meta-data
android:name="xposedminversion"
android:value="93" />
<meta-data
android:name="xposedscope"
android:resource="@array/xposed_scope" />
</application>
</manifest>
+1
View File
@@ -0,0 +1 @@
top.littlew.acer.XposedInit
@@ -0,0 +1,187 @@
package top.littlew.acer;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.UserHandle;
import java.util.ArrayList;
import java.util.List;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class XposedInit implements IXposedHookLoadPackage {
@SuppressLint("StaticFieldLeak")
private static Context mContext;
private static final int AVAILABLE = 0;
private static final int CLONED_APPS_SUMMARY_STRING_ID = 0x7f140736;
private static Class<?> UtilsClass;
@Override
public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) {
if (!lpparam.packageName.equals("com.android.settings")) {
return;
}
UtilsClass = XposedHelpers.findClass("com.android.settings.Utils", lpparam.classLoader);
XposedHelpers.findAndHookMethod(
XposedHelpers.findClass("com.android.settings.SettingsActivity", lpparam.classLoader),
"onCreate",
Bundle.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
mContext = (Context) param.thisObject;
}
}
);
XposedHelpers.findAndHookMethod(XposedHelpers.findClass("android.os.Flags", lpparam.classLoader), "allowPrivateProfile", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(true);
}
});
Class<?> clonedAppsPreferenceControllerClass = XposedHelpers.findClass("com.android.settings.applications.ClonedAppsPreferenceController", lpparam.classLoader);
XposedHelpers.findAndHookMethod(
clonedAppsPreferenceControllerClass,
"getAvailabilityStatus",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(AVAILABLE);
}
});
XposedHelpers.findAndHookMethod(
clonedAppsPreferenceControllerClass,
"updatePreferenceSummary",
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
// Allowing native logic to calculate Summary.
}
});
XposedHelpers.findAndHookMethod(
clonedAppsPreferenceControllerClass,
"updateSummary",
int.class,
int.class,
new XC_MethodHook() {
@SuppressLint("QueryPermissionsNeeded")
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (mContext == null) {
return;
}
PackageManager pm = mContext.getPackageManager();
List<String> primaryUserNonSystemAppPackages = new ArrayList<>();
for (PackageInfo pkgInfo : pm.getInstalledPackages(PackageManager.GET_ACTIVITIES)) {
if ((pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 &&
!pkgInfo.packageName.equals(lpparam.packageName)) {
primaryUserNonSystemAppPackages.add(pkgInfo.packageName);
}
}
int currentlyClonedCount = 0;
int cloneUserId = getCloneUserIDInternal();
if (cloneUserId > 0) {
@SuppressLint("PackageManagerGetInstalledPackagesAsUser")
List<PackageInfo> cloneUserPackages = (List<PackageInfo>) XposedHelpers.callMethod(pm, "getInstalledPackagesAsUser", PackageManager.GET_ACTIVITIES, cloneUserId);
for (PackageInfo clonedPkgInfo : cloneUserPackages) {
if ((clonedPkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 &&
primaryUserNonSystemAppPackages.contains(clonedPkgInfo.packageName)) {
currentlyClonedCount++;
}
}
}
Object mPreference = XposedHelpers.getObjectField(param.thisObject, "mPreference");
if (mPreference != null) {
String newSummary = String.format(mContext.getResources().getString(CLONED_APPS_SUMMARY_STRING_ID), currentlyClonedCount, primaryUserNonSystemAppPackages.size());
XposedHelpers.callMethod(mPreference, "setSummary", newSummary);
}
param.setResult(null);
}
});
XposedHelpers.findAndHookMethod(
XposedHelpers.findClass("com.android.settings.applications.AppStateClonedAppsBridge", lpparam.classLoader),
"updateExtraInfo",
XposedHelpers.findClass("com.android.settingslib.applications.ApplicationsState$AppEntry", lpparam.classLoader),
String.class,
int.class,
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) {
Object appEntry = param.args[0];
String pkg = (String) param.args[1];
ApplicationInfo appInfo = (ApplicationInfo) XposedHelpers.getObjectField(appEntry, "info");
int appEntryUserId = (int) XposedHelpers.callStaticMethod(UserHandle.class, "getUserId", appInfo.uid);
int cloneUserId = getCloneUserIDInternal();
Boolean shouldDisplay;
if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
shouldDisplay = Boolean.FALSE;
} else {
List<String> cloneProfileNonSystemAppPackages = getStrings(cloneUserId);
if (appEntryUserId == cloneUserId) {
shouldDisplay = Boolean.TRUE;
} else if (appEntryUserId == (int) XposedHelpers.callStaticMethod(UserHandle.class, "myUserId")) {
if (!cloneProfileNonSystemAppPackages.contains(pkg)) {
shouldDisplay = Boolean.TRUE;
} else {
shouldDisplay = Boolean.FALSE;
}
} else {
shouldDisplay = Boolean.FALSE;
}
}
XposedHelpers.setObjectField(appEntry, "extraInfo", shouldDisplay);
}
});
}
private static List<String> getStrings(int cloneUserId) {
List<String> cloneProfileNonSystemAppPackages = new ArrayList<>();
if (cloneUserId > 0 && mContext != null) {
@SuppressLint("PackageManagerGetInstalledPackagesAsUser")
List<PackageInfo> installedClonedPackages = (List<PackageInfo>) XposedHelpers.callMethod(mContext.getPackageManager(), "getInstalledPackagesAsUser", PackageManager.GET_ACTIVITIES, cloneUserId);
if (installedClonedPackages != null) {
for (PackageInfo pInfo : installedClonedPackages) {
if ((pInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
cloneProfileNonSystemAppPackages.add(pInfo.packageName);
}
}
}
}
return cloneProfileNonSystemAppPackages;
}
private int getCloneUserIDInternal() {
if (UtilsClass == null || mContext == null) {
return -1;
}
return (int) XposedHelpers.callStaticMethod(UtilsClass, "getCloneUserId", mContext);
}
}
@@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<group
android:scaleX="0.4"
android:scaleY="0.4"
android:pivotX="480"
android:pivotY="480">
<path
android:fillColor="@android:color/white"
android:pathData="M560,640L640,640L640,520L760,520L760,440L640,440L640,320L560,320L560,440L440,440L440,520L560,520L560,640ZM240,820Q131,782 65.5,688.5Q0,595 0,480Q0,365 65.5,271.5Q131,178 240,140L240,228Q166,263 123,331Q80,399 80,480Q80,561 123,629Q166,697 240,732L240,820ZM600,840Q525,840 459.5,811.5Q394,783 345.5,734.5Q297,686 268.5,620.5Q240,555 240,480Q240,405 268.5,339.5Q297,274 345.5,225.5Q394,177 459.5,148.5Q525,120 600,120Q675,120 740.5,148.5Q806,177 854.5,225.5Q903,274 931.5,339.5Q960,405 960,480Q960,555 931.5,620.5Q903,686 854.5,734.5Q806,783 740.5,811.5Q675,840 600,840ZM600,480Q600,480 600,480Q600,480 600,480Q600,480 600,480Q600,480 600,480Q600,480 600,480Q600,480 600,480Q600,480 600,480Q600,480 600,480ZM600,760Q717,760 798.5,678.5Q880,597 880,480Q880,363 798.5,281.5Q717,200 600,200Q483,200 401.5,281.5Q320,363 320,480Q320,597 401.5,678.5Q483,760 600,760Z"/>
</group>
</vector>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background_color"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
+6
View File
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="xposed_scope">
<item>com.android.settings</item>
</string-array>
</resources>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<color name="ic_launcher_background_color">#FBBF24</color>
</resources>