├── .github └── workflows │ └── android.yml ├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ └── xposed_init │ ├── java │ └── io │ │ └── github │ │ └── icepony │ │ └── alwayscreateuser │ │ ├── MainActivity.java │ │ ├── MainHook.java │ │ ├── SettingsFragment.java │ │ └── XposedHelper.java │ └── res │ ├── values-night │ └── themes.xml │ ├── values │ ├── colors.xml │ ├── strings.xml │ └── themes.xml │ └── xml │ └── prefs.xml ├── build.gradle ├── docs └── img │ ├── Island.png │ └── Thanox.png ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v3 17 | 18 | - name: Set up JDK 21 19 | uses: actions/setup-java@v3 20 | with: 21 | java-version: 21 22 | distribution: 'temurin' 23 | cache: 'gradle' 24 | 25 | - name: Build with Gradle 26 | run: | 27 | chmod +x ./gradlew 28 | ./gradlew assembleRelease assembleDebug 29 | 30 | - name: Upload artifact 31 | uses: actions/upload-artifact@v4 32 | with: 33 | name: Signed app bundle 34 | path: app/build/outputs/apk 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Always create user 2 | 3 | [![Xposed Module](https://img.shields.io/badge/Xposed%20Module-✓-green.svg)]() 4 | [![Android Version](https://img.shields.io/badge/Android-4.2%2B-blue.svg)]() 5 | [![GitHub issues](https://img.shields.io/github/issues/icepony/AlwaysCreateUser)](https://github.com/icepony/AlwaysCreateUser/issues) 6 | 7 | An Xposed Framework module that bypasses Android's user/profile creation limits 8 | 9 | ## Features 10 | 11 | - Bypass common profile creation errors 12 | - `Cannot add more profiles of type android.os.usertype.profile.(MANAGED | CLONE | PRIVATE) for user 13 | 0` 14 | - `Maximum user limit is reached` 15 | - `Cannot add more managed profiles for user` 16 | - `Error: couldn't create User` 17 | - Support Android 4.2 through Android 14+ 18 | - Compatible with popular profile manager apps (Island, Shelter, etc.) 19 | 20 | ## Compatibility Overview 21 | 22 | The module hooks methods within [ 23 | `UserManagerService.java`](https://github.com/aosp-mirror/platform_frameworks_base/blob/54642d141f80d495a475b304052eedd2832fcdb1/services/core/java/com/android/server/pm/UserManagerService.java#L5733) 24 | 25 | | Android Version | Hook Methods List | 26 | |-----------------|-----------------------------| 27 | | 14+ | `isCreationOverrideEnabled` | 28 | | 11-13 | `canAddMoreProfilesToUser` | 29 | | 7-13 | `isUserLimitReached` | 30 | | 6-10 | `canAddMoreManagedProfiles` | 31 | | 4.2-6 | `isUserLimitReachedLocked` | 32 | 33 | ## Screenshot 34 | 35 | | [Island](https://github.com/oasisfeng/island) | [Thanox](https://github.com/Tornaco/Thanox) | 36 | |-----------------------------------------------|---------------------------------------------| 37 | | ![Island](/docs/img/Island.png) | ![Thanox](/docs/img/Thanox.png) | 38 | 39 | ### [How to Setup for Island](https://island.oasisfeng.com/setup.html#manual-setup-for-island) 40 | 41 | ## Check Out My Other Project! 42 | 43 | * **[AlwaysBatterySaver](https://github.com/icepony/AlwaysBatterySaver)**: An Xposed module that 44 | prevents Android from automatically disabling Battery Saver mode when the device is charging. 45 | 46 | ## Thanks 47 | 48 | * Xposed Framework Developers 49 | * [CorePatch](https://github.com/LSPosed/CorePatch) (Inspiration for hook structure) 50 | * LLMs (Gemini, DeepSeek, ChatGPT) for assistance. 51 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) 3 | } 4 | 5 | android { 6 | buildFeatures { 7 | buildConfig = true 8 | } 9 | 10 | namespace 'io.github.icepony.alwayscreateuser' 11 | compileSdk 35 12 | 13 | defaultConfig { 14 | applicationId "io.github.icepony.alwayscreateuser" 15 | minSdk 17 16 | targetSdk 35 17 | versionCode 7 18 | versionName "2.0" 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled true 24 | shrinkResources true 25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 26 | } 27 | } 28 | 29 | compileOptions { 30 | sourceCompatibility JavaVersion.VERSION_11 31 | targetCompatibility JavaVersion.VERSION_11 32 | } 33 | 34 | android.applicationVariants.configureEach { variant -> 35 | variant.outputs.configureEach { 36 | outputFileName = "AlwaysCreateUser_v${variant.versionName}-${variant.buildType.name}.apk" 37 | } 38 | } 39 | } 40 | 41 | dependencies { 42 | //noinspection GradleDependency,UseTomlInstead 43 | compileOnly("de.robv.android.xposed:api:53") 44 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep class io.github.icepony.alwayscreateuser.MainHook 24 | 25 | -repackageclasses 26 | -allowaccessmodification 27 | -overloadaggressively -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 13 | 16 | 19 | 22 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/assets/xposed_init: -------------------------------------------------------------------------------- 1 | io.github.icepony.alwayscreateuser.MainHook -------------------------------------------------------------------------------- /app/src/main/java/io/github/icepony/alwayscreateuser/MainActivity.java: -------------------------------------------------------------------------------- 1 | package io.github.icepony.alwayscreateuser; 2 | 3 | import android.app.Activity; 4 | import android.app.AlertDialog; 5 | import android.content.Context; 6 | import android.os.Bundle; 7 | 8 | public class MainActivity extends Activity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | 14 | try { 15 | getSharedPreferences(BuildConfig.APPLICATION_ID + "_preferences", Context.MODE_WORLD_READABLE); 16 | } catch (SecurityException ignored) { 17 | new AlertDialog.Builder(this) 18 | .setTitle(R.string.module_not_loaded) 19 | .setMessage(R.string.will_not_save) 20 | .setPositiveButton(android.R.string.ok, null) 21 | .show(); 22 | } 23 | 24 | if (savedInstanceState == null) { 25 | getFragmentManager() 26 | .beginTransaction() 27 | .add(android.R.id.content, new SettingsFragment()) 28 | .commit(); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/icepony/alwayscreateuser/MainHook.java: -------------------------------------------------------------------------------- 1 | package io.github.icepony.alwayscreateuser; 2 | 3 | import de.robv.android.xposed.IXposedHookLoadPackage; 4 | import de.robv.android.xposed.XC_MethodHook; 5 | import de.robv.android.xposed.callbacks.XC_LoadPackage; 6 | 7 | public class MainHook extends XposedHelper implements IXposedHookLoadPackage { 8 | 9 | boolean isModuleEnabled; 10 | private Class userManagerServiceClass; 11 | 12 | private void reloadPreferences() { 13 | prefs.reload(); 14 | isModuleEnabled = prefs.getBoolean("enable_module", true); 15 | } 16 | 17 | @Override 18 | public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) { 19 | if (!lpparam.packageName.equals("android")) return; 20 | log("Handling Android package"); 21 | userManagerServiceClass = findClass("com.android.server.pm.UserManagerService", lpparam.classLoader); 22 | if (userManagerServiceClass == null) 23 | return; 24 | 25 | hookUserManager(); 26 | } 27 | 28 | private void hookUserManager() { 29 | checkAndHook("isCreationOverrideEnabled", true); 30 | checkAndHook("canAddMoreProfilesToUser", true); 31 | checkAndHook("canAddMoreManagedProfiles", true); 32 | checkAndHook("isUserLimitReached", false); 33 | checkAndHook("isUserLimitReachedLocked", false); 34 | } 35 | 36 | private void checkAndHook(String methodName, Object result) { 37 | hookAllMethods(userManagerServiceClass, methodName, new XC_MethodHook() { 38 | @Override 39 | protected void beforeHookedMethod(MethodHookParam param) { 40 | reloadPreferences(); 41 | if (!isModuleEnabled) { 42 | return; 43 | } 44 | 45 | if (prefs.getBoolean("hook_" + methodName, true)) { 46 | log("Bypassing " + methodName); 47 | param.setResult(result); 48 | } 49 | } 50 | }); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/icepony/alwayscreateuser/SettingsFragment.java: -------------------------------------------------------------------------------- 1 | package io.github.icepony.alwayscreateuser; 2 | 3 | import android.os.Build; 4 | import android.os.Bundle; 5 | import android.preference.Preference; 6 | import android.preference.PreferenceCategory; 7 | import android.preference.PreferenceFragment; 8 | import android.preference.SwitchPreference; 9 | import android.view.View; 10 | 11 | public class SettingsFragment extends PreferenceFragment implements Preference.OnPreferenceChangeListener { 12 | 13 | private SwitchPreference mEnableModuleSwitch; 14 | private PreferenceCategory mSettingsPreferenceCategory; 15 | 16 | @Override 17 | public void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | 20 | addPreferencesFromResource(R.xml.prefs); 21 | 22 | mEnableModuleSwitch = (SwitchPreference) findPreference("enable_module"); 23 | mSettingsPreferenceCategory = (PreferenceCategory) findPreference("settings"); 24 | 25 | updatePreferenceEnabledStates(mEnableModuleSwitch.isChecked()); 26 | 27 | mEnableModuleSwitch.setOnPreferenceChangeListener(this); 28 | } 29 | 30 | private void updatePreferenceEnabledStates(boolean moduleEnabled) { 31 | if (!BuildConfig.DEBUG) { 32 | if (mSettingsPreferenceCategory != null) { 33 | mSettingsPreferenceCategory.setEnabled(moduleEnabled); 34 | } 35 | } 36 | } 37 | 38 | @Override 39 | public boolean onPreferenceChange(Preference preference, Object newValue) { 40 | if (!(newValue instanceof Boolean)) { 41 | return false; 42 | } 43 | boolean isEnabled = (Boolean) newValue; 44 | String key = preference.getKey(); 45 | 46 | switch (key) { 47 | case "enable_module": 48 | updatePreferenceEnabledStates(isEnabled); 49 | return true; 50 | 51 | default: 52 | return true; 53 | } 54 | } 55 | 56 | @Override 57 | public void onViewCreated(View view, Bundle savedInstanceState) { 58 | // Adjust the top distance to avoid conflict with the ActionBar 59 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 60 | view.setOnApplyWindowInsetsListener((v, insets) -> { 61 | view.setPadding(insets.getSystemWindowInsetLeft(), 62 | insets.getSystemWindowInsetTop(), 63 | insets.getSystemWindowInsetRight(), 64 | insets.getStableInsetBottom()); 65 | 66 | return insets.consumeSystemWindowInsets(); 67 | }); 68 | } 69 | super.onViewCreated(view, savedInstanceState); 70 | } 71 | 72 | @Override 73 | public void onResume() { 74 | super.onResume(); 75 | if (mEnableModuleSwitch != null) { 76 | updatePreferenceEnabledStates(mEnableModuleSwitch.isChecked()); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/icepony/alwayscreateuser/XposedHelper.java: -------------------------------------------------------------------------------- 1 | package io.github.icepony.alwayscreateuser; 2 | 3 | import android.util.Log; 4 | 5 | import java.lang.reflect.Field; 6 | import java.lang.reflect.Member; 7 | import java.util.Set; 8 | 9 | import de.robv.android.xposed.XC_MethodHook; 10 | import de.robv.android.xposed.XSharedPreferences; 11 | import de.robv.android.xposed.XposedBridge; 12 | import de.robv.android.xposed.XposedHelpers; 13 | 14 | public class XposedHelper { 15 | public static final String TAG = "AlwaysCreateUser"; 16 | public static XSharedPreferences prefs = new XSharedPreferences(BuildConfig.APPLICATION_ID, BuildConfig.APPLICATION_ID + "_preferences"); 17 | 18 | public static Field findField(Class clazz, String fieldName) { 19 | try { 20 | Field field = XposedHelpers.findField(clazz, fieldName); 21 | log("Successfully found field on class " + clazz.getName() + ": " + fieldName); 22 | return field; 23 | } catch (Throwable e) { 24 | logError("Error finding field on class " + clazz.getName() + ": " + fieldName, e); 25 | } 26 | return null; 27 | } 28 | 29 | public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) { 30 | try { 31 | XC_MethodHook.Unhook unhook = XposedBridge.hookMethod(hookMethod, callback); 32 | log("Successfully added hook for " + hookMethod); 33 | return unhook; 34 | } catch (Throwable e) { 35 | logError("Error hook method: " + hookMethod, e); 36 | } 37 | return null; 38 | } 39 | 40 | public static XC_MethodHook.Unhook findAndHookMethod(String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback) { 41 | try { 42 | XC_MethodHook.Unhook andHookMethod = XposedHelpers.findAndHookMethod(className, classLoader, methodName, parameterTypesAndCallback); 43 | log("Successfully added hook for " + className + "#" + methodName); 44 | return andHookMethod; 45 | } catch (Throwable e) { 46 | logError("Error hook method: " + className + "#" + methodName, e); 47 | } 48 | return null; 49 | } 50 | 51 | public static Set hookAllMethods(Class hookClass, String methodName, XC_MethodHook callback) { 52 | try { 53 | Set unhooks = XposedBridge.hookAllMethods(hookClass, methodName, callback); 54 | if (unhooks.isEmpty()) { 55 | log("No hooks found for " + hookClass.getName() + "#" + methodName); 56 | return null; 57 | } 58 | log("Successfully added hook for " + hookClass.getName() + "#" + methodName + " with " + unhooks.size() + " hooks."); 59 | return unhooks; 60 | } catch (Throwable e) { 61 | logError("Error hook method: " + hookClass.getName() + "#" + methodName, e); 62 | } 63 | return null; 64 | } 65 | 66 | public static Class findClass(String className, ClassLoader classLoader) { 67 | try { 68 | Class aClass = XposedHelpers.findClass(className, classLoader); 69 | log("Successfully found class: " + className); 70 | return aClass; 71 | } catch (Throwable e) { 72 | logError("Error finding class: " + className, e); 73 | } 74 | return null; 75 | } 76 | 77 | public static void logError(String message, Throwable t) { 78 | XposedBridge.log("E/" + TAG + ": " + message + "\n" + Log.getStackTraceString(t)); 79 | } 80 | 81 | public static void log(String message) { 82 | if (BuildConfig.DEBUG) { 83 | XposedBridge.log("D/" + TAG + ": " + message); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |