1 Commits
Author SHA1 Message Date
LittleChest fd404114d8 Init 1.1 2025-08-19 20:31:48 +08:00
17 changed files with 122 additions and 248 deletions
+1 -6
View File
@@ -1,12 +1,7 @@
*.iml *.iml
.gradle .gradle
/local.properties /local.properties
/.idea/caches /.idea
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store .DS_Store
/build /build
/captures /captures
-3
View File
@@ -1,3 +0,0 @@
# 默认忽略的文件
/shelf/
/workspace.xml
Generated
-1
View File
@@ -1 +0,0 @@
Double Tap To Wake
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>
-19
View File
@@ -1,19 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>
-10
View File
@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>
-9
View File
@@ -1,9 +0,0 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>
-17
View File
@@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+3 -3
View File
@@ -8,10 +8,10 @@ android {
defaultConfig { defaultConfig {
applicationId = "top.littlew.acer" applicationId = "top.littlew.acer"
minSdk = 33 minSdk = 34
targetSdk = 36 targetSdk = 36
versionCode = 1 versionCode = 2
versionName = "1.0" versionName = "1.1"
} }
buildTypes { buildTypes {
+1 -1
View File
@@ -10,7 +10,7 @@
android:value="true" /> android:value="true" />
<meta-data <meta-data
android:name="xposeddescription" android:name="xposeddescription"
android:value="Enable App Clone for all user apps." /> android:value="Enable App Clone for all or custom user apps." />
<meta-data <meta-data
android:name="xposedminversion" android:name="xposedminversion"
android:value="93" /> android:value="93" />
@@ -2,14 +2,20 @@ package top.littlew.acer;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserHandle; import android.os.UserHandle;
import android.view.Menu;
import android.view.View;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.function.Predicate;
import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XC_MethodHook;
@@ -20,69 +26,28 @@ public class XposedInit implements IXposedHookLoadPackage {
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
private static Context mContext; 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 @Override
public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) { 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); if (lpparam.packageName.equals("com.android.settings")) {
XposedHelpers.findAndHookMethod(XposedHelpers.findClass("com.android.settings.SettingsActivity", lpparam.classLoader), "onCreate", Bundle.class, new XC_MethodHook() {
XposedHelpers.findAndHookMethod(
XposedHelpers.findClass("com.android.settings.SettingsActivity", lpparam.classLoader),
"onCreate",
Bundle.class,
new XC_MethodHook() {
@Override @Override
protected void afterHookedMethod(MethodHookParam param) { protected void afterHookedMethod(MethodHookParam param) {
mContext = (Context) param.thisObject; 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); Class<?> clonedAppsPreferenceControllerClass = XposedHelpers.findClass("com.android.settings.applications.ClonedAppsPreferenceController", lpparam.classLoader);
XposedHelpers.findAndHookMethod( XposedHelpers.findAndHookMethod(clonedAppsPreferenceControllerClass, "getAvailabilityStatus", new XC_MethodHook() {
clonedAppsPreferenceControllerClass,
"getAvailabilityStatus",
new XC_MethodHook() {
@Override @Override
protected void beforeHookedMethod(MethodHookParam param) { protected void beforeHookedMethod(MethodHookParam param) {
param.setResult(AVAILABLE); param.setResult(0);
} }
}); });
XposedHelpers.findAndHookMethod( XposedHelpers.findAndHookMethod(clonedAppsPreferenceControllerClass, "updateSummary", int.class, int.class, new XC_MethodHook() {
clonedAppsPreferenceControllerClass, @SuppressLint({"QueryPermissionsNeeded", "DiscouragedApi"})
"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 @Override
protected void beforeHookedMethod(MethodHookParam param) { protected void beforeHookedMethod(MethodHookParam param) {
if (mContext == null) { if (mContext == null) {
@@ -91,97 +56,96 @@ public class XposedInit implements IXposedHookLoadPackage {
PackageManager pm = mContext.getPackageManager(); PackageManager pm = mContext.getPackageManager();
List<String> primaryUserNonSystemAppPackages = new ArrayList<>(); List<String> cloneableApps = getCloneableApps();
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; List<PackageInfo> primaryInstalledPackagesAsUser = (List<PackageInfo>) XposedHelpers.callMethod(pm, "getInstalledPackagesAsUser", 0, XposedHelpers.callStaticMethod(UserHandle.class, "myUserId"));
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) { List<String> primaryUserApps = primaryInstalledPackagesAsUser.stream().map(x -> x.packageName).toList();
if ((clonedPkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0 &&
primaryUserNonSystemAppPackages.contains(clonedPkgInfo.packageName)) { int availableAppsCount = (int) cloneableApps.stream().filter(primaryUserApps::contains).count();
currentlyClonedCount++;
} int clonedUserId = (int) XposedHelpers.callStaticMethod(XposedHelpers.findClass("com.android.settings.Utils", lpparam.classLoader), "getCloneUserId", mContext);
} int clonedAppsCount = 0;
if (!(clonedUserId == -1)) {
List<PackageInfo> cloneProfileInstalledPackagesAsUser = (List<PackageInfo>) XposedHelpers.callMethod(pm, "getInstalledPackagesAsUser", 0, clonedUserId);
List<String> cloneProfileApps = cloneProfileInstalledPackagesAsUser.stream().map(x -> x.packageName).toList();
clonedAppsCount = (int) cloneableApps.stream().filter(cloneProfileApps::contains).count();
} }
Object mPreference = XposedHelpers.getObjectField(param.thisObject, "mPreference"); Object mPreference = XposedHelpers.getObjectField(param.thisObject, "mPreference");
if (mPreference != null) { if (mPreference != null) {
String newSummary = String.format(mContext.getResources().getString(CLONED_APPS_SUMMARY_STRING_ID), currentlyClonedCount, primaryUserNonSystemAppPackages.size()); XposedHelpers.callMethod(mPreference, "setSummary", String.format(mContext.getResources().getString(mContext.getResources().getIdentifier("cloned_apps_summary", "string", mContext.getPackageName())), clonedAppsCount, availableAppsCount - clonedAppsCount));
XposedHelpers.callMethod(mPreference, "setSummary", newSummary);
} }
param.setResult(null); param.setResult(null);
} }
}); });
XposedHelpers.findAndHookMethod( 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() {
XposedHelpers.findClass("com.android.settings.applications.AppStateClonedAppsBridge", lpparam.classLoader), @Override
"updateExtraInfo", protected void beforeHookedMethod(MethodHookParam param) {
XposedHelpers.findClass("com.android.settingslib.applications.ApplicationsState$AppEntry", lpparam.classLoader), XposedHelpers.setObjectField(param.thisObject, "mAllowedApps", getCloneableApps());
String.class, }
int.class, });
new XC_MethodHook() {
XposedHelpers.findAndHookMethod(XposedHelpers.findClass("com.android.settings.applications.manageapplications.ManageApplications", lpparam.classLoader), "updateOptionsMenu", new XC_MethodHook() {
@SuppressLint("DiscouragedApi")
@Override @Override
protected void afterHookedMethod(MethodHookParam param) { protected void afterHookedMethod(MethodHookParam param) {
Object appEntry = param.args[0]; if (XposedHelpers.getObjectField(param.thisObject, "mListType").equals(17) && (int) XposedHelpers.callStaticMethod(XposedHelpers.findClass("com.android.settings.Utils", lpparam.classLoader), "getCloneUserId", mContext) > 0) {
String pkg = (String) param.args[1]; Menu mOptionsMenu = (Menu) XposedHelpers.getObjectField(param.thisObject, "mOptionsMenu");
ApplicationInfo appInfo = (ApplicationInfo) XposedHelpers.getObjectField(appEntry, "info"); if (mOptionsMenu != null) {
mOptionsMenu.findItem(mContext.getResources().getIdentifier("delete_all_app_clones", "id", mContext.getPackageName())).setVisible(true);
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); }
});
} else {
XposedHelpers.findAndHookMethod(XposedHelpers.findClass("com.android.launcher3.allapps.ActivityAllAppsContainerView$AdapterHolder", lpparam.classLoader), "setup", View.class, Predicate.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if ((int) XposedHelpers.getObjectField(param.thisObject, "mType") == 0) {
Object allAppsContainerView = XposedHelpers.getObjectField(param.thisObject, "this$0");
Predicate workProfileMatcher = (Predicate) XposedHelpers.callMethod(XposedHelpers.getObjectField(allAppsContainerView, "mWorkManager"), "getItemInfoMatcher");
Predicate privateProfileMatcher = (Predicate) XposedHelpers.callMethod(XposedHelpers.getObjectField(allAppsContainerView, "mPrivateProfileManager"), "getItemInfoMatcher");
Predicate WorkOrPrivate = workProfileMatcher.or(privateProfileMatcher);
Predicate filter = WorkOrPrivate.negate();
param.args[1] = filter;
}
} }
}); });
} }
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() { private List<String> getCloneableApps() {
if (UtilsClass == null || mContext == null) { if (mContext == null) {
return -1; return new ArrayList<>();
} }
return (int) XposedHelpers.callStaticMethod(UtilsClass, "getCloneUserId", mContext);
@SuppressLint("DiscouragedApi") List<String> cloneableApps = new ArrayList<>(Arrays.asList(mContext.getResources().getStringArray(mContext.getResources().getIdentifier("cloneable_apps", "array", "android"))));
SharedPreferences sharedPrefs = mContext.getSharedPreferences("cloneable_apps", Context.MODE_PRIVATE);
List<String> newCloneableApps = new ArrayList<>(sharedPrefs.getStringSet("cloneable_apps", new HashSet<>()));
cloneableApps.addAll(newCloneableApps);
if (cloneableApps.isEmpty()) {
List<PackageInfo> primaryInstalledPackagesAsUser = (List<PackageInfo>) XposedHelpers.callMethod(mContext.getPackageManager(), "getInstalledPackagesAsUser", 0, XposedHelpers.callStaticMethod(UserHandle.class, "myUserId"));
for (PackageInfo pkgInfo : primaryInstalledPackagesAsUser) {
if ((pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
cloneableApps.add(pkgInfo.packageName);
}
}
}
return cloneableApps;
} }
} }
@@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp" android:width="48dp"
android:height="24dp" android:height="48dp"
android:viewportWidth="960" android:viewportWidth="960"
android:viewportHeight="960"> android:viewportHeight="960">
<group <group
@@ -10,6 +10,6 @@
android:pivotY="480"> android:pivotY="480">
<path <path
android:fillColor="@android:color/white" 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"/> android:pathData="M571,632L631,632L631,508L754,508L754,448L631,448L631,325L571,325L571,448L447,448L447,508L571,508L571,632ZM233,816Q128,774 64,682.74Q0,591.48 0,479.24Q0,367 64.5,276Q129,185 233,143L233,211Q154,247 107,319.7Q60,392.4 60,479Q60,567 107,639Q154,711 233,748L233,816ZM600,839Q525,839 459.5,810.5Q394,782 345.5,733.5Q297,685 268.5,619.5Q240,554 240,479Q240,404 268.5,338.5Q297,273 345.5,224.5Q394,176 459.5,147.5Q525,119 600,119Q675,119 740.5,147.5Q806,176 854.5,224.5Q903,273 931.5,338.5Q960,404 960,479Q960,554 931.5,619.5Q903,685 854.5,733.5Q806,782 740.5,810.5Q675,839 600,839ZM600,479Q600,479 600,479Q600,479 600,479Q600,479 600,479Q600,479 600,479Q600,479 600,479Q600,479 600,479Q600,479 600,479Q600,479 600,479ZM600.5,779Q725,779 812.5,691.68Q900,604.36 900,479Q900,355 812.68,267Q725.36,179 600,179Q476,179 388,267Q300,355 300,479Q300,604.36 388,691.68Q476,779 600.5,779Z"/>
</group> </group>
</vector> </vector>
+2
View File
@@ -2,5 +2,7 @@
<resources> <resources>
<string-array name="xposed_scope"> <string-array name="xposed_scope">
<item>com.android.settings</item> <item>com.android.settings</item>
<item>com.android.launcher3</item>
<item>com.google.android.apps.nexuslauncher</item>
</string-array> </string-array>
</resources> </resources>
+1 -1
View File
@@ -1,5 +1,5 @@
[versions] [versions]
agp = "8.12.0" agp = "8.12.1"
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }