├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── app-process ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── com │ │ └── rosan │ │ └── app_process │ │ ├── INewProcess.aidl │ │ └── IRemoteProcess.aidl │ └── java │ └── com │ └── rosan │ └── app_process │ ├── AppProcess.java │ ├── BinderWrapper.java │ ├── NewProcess.java │ ├── NewProcessImpl.java │ ├── NewProcessReceiver.java │ ├── RemoteProcess.java │ └── RemoteProcessImpl.java ├── app ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── rosan │ │ └── app_process │ │ └── demo │ │ └── MainActivity.java │ └── res │ └── layout │ └── activity_main.xml ├── build.gradle ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── hidden-api ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── android │ ├── app │ ├── ActivityManagerNative.java │ ├── ActivityThread.java │ ├── ContentProviderHolder.java │ ├── ContextImpl.java │ ├── IActivityManager.java │ └── LoadedApk.java │ ├── content │ └── IContentProvider.java │ └── os │ └── ServiceManager.java └── settings.gradle /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.bat text eol=crlf 3 | *.jar binary 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gradle 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "05:00" 8 | target-branch: main 9 | registries: 10 | - maven-google 11 | - maven-central 12 | - gradle-plugin 13 | groups: 14 | maven-dependencies: 15 | patterns: 16 | - "*" 17 | 18 | registries: 19 | maven-google: 20 | type: maven-repository 21 | url: "https://dl.google.com/dl/android/maven2/" 22 | maven-central: 23 | type: maven-repository 24 | url: "https://repo1.maven.org/maven2/" 25 | gradle-plugin: 26 | type: maven-repository 27 | url: "https://plugins.gradle.org/m2/" 28 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | paths-ignore: 6 | - '*.md' 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | name: Build 13 | runs-on: ubuntu-24.04 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Setup Java 20 | uses: actions/setup-java@v4 21 | with: 22 | distribution: 'temurin' 23 | java-version: 21 24 | 25 | - name: Setup Gradle 26 | uses: gradle/actions/setup-gradle@v4 27 | 28 | - name: Set environment 29 | run: | 30 | { 31 | echo "repo=$(echo ${GITHUB_REPOSITORY#$GITHUB_REPOSITORY_OWNER/})" 32 | echo "version=v$(grep artifactVersion build.gradle| awk -F\" '{print $2}')" 33 | echo "commit=$(echo ${{ github.sha }} | cut -c-7)" 34 | } >> $GITHUB_ENV 35 | 36 | - name: Build 37 | run: | 38 | ./gradlew --no-daemon --warning-mode=all assembleDebug 39 | cp app-process/build/outputs/aar/app-process-debug.aar AndroidAppProcess-${{ env.version }}@${{ env.commit }}.aar 40 | cp app/build/outputs/apk/debug/app-debug.apk AppProcessDemo@${{ env.commit }}.apk 41 | 42 | - name: Upload 43 | uses: actions/upload-artifact@v4 44 | with: 45 | name: "${{ env.repo }}-${{ env.version }}@${{ env.commit }}" 46 | path: | 47 | AndroidAppProcess-${{ env.version }}@${{ env.commit }}.aar 48 | AppProcessDemo@${{ env.commit }}.apk 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle 2 | /.gradle/ 3 | # Android SDK 4 | /local.properties 5 | # Gradle Build 6 | /*/build/ 7 | # AGP Lint 8 | /build/ 9 | # IntelliJ 10 | /.idea/ 11 | # VS Code 12 | /.vscode/ 13 | # Keystore 14 | /keystore/ 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android AppProcess 2 | 3 | Encapsulation of Android app_process, you can use it just like Android Service. 4 | 5 | ![Maven Central](https://img.shields.io/maven-central/v/io.github.iamr0s/AndroidAppProcess) 6 | 7 | ### How to use it 8 | 9 | ### Import library 10 | 11 | ```Groovy 12 | implementation 'io.github.iamr0s:AndroidAppProcess:' 13 | ``` 14 | 15 | ### 1. New Process 16 | 17 | - Default 18 | 19 | ```java 20 | AppProcess process = new AppProcess.Default(); 21 | process.init(context.getPackageName()); 22 | ``` 23 | 24 | - Root 25 | 26 | ```java 27 | AppProcess process = new AppProcess.Root(); 28 | process.init(context.getPackageName()); 29 | ``` 30 | 31 | ### 2. Use it. 32 | 33 | - Remote Binder Transact 34 | 35 | ```java 36 | AppProcess process = new AppProcess.Root(); 37 | process.init(context.getPackageName()); 38 | 39 | IPackageManager manager = android.os.ServiceManager.getService("package"); 40 | IBinder binderWrapper = process.binderWrapper(manager.asBinder()); 41 | IPackageManager managerWrapper = IPackageManager.Stub.asInterface(binderWrapper); 42 | 43 | managerWrapper.uninstall(...) // will call it in root. 44 | ``` 45 | 46 | - More 47 | 48 | > See the demo. 49 | 50 | ### 3. Close 51 | 52 | > You must close the AppProcess after use it. 53 | 54 | ```java 55 | process.close() 56 | ``` 57 | -------------------------------------------------------------------------------- /app-process/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.agp.lib) 3 | id "maven-publish" 4 | id "signing" 5 | } 6 | 7 | def ossrhUsername = '' 8 | def ossrhPassword = '' 9 | def extGradlePath = rootDir.path + "/keystore/r0s.gradle" 10 | if (file(extGradlePath).exists()) { 11 | apply from: extGradlePath 12 | } 13 | 14 | android { 15 | namespace = "com.rosan.app_process" 16 | compileSdk = 35 17 | 18 | defaultConfig { 19 | minSdk = 19 20 | } 21 | 22 | buildFeatures { 23 | buildConfig = true 24 | aidl = true 25 | } 26 | 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_21 29 | targetCompatibility JavaVersion.VERSION_21 30 | } 31 | 32 | publishing { 33 | singleVariant("release") { 34 | withSourcesJar() 35 | withJavadocJar() 36 | } 37 | } 38 | } 39 | 40 | dependencies { 41 | compileOnly project(":hidden-api") 42 | compileOnly(libs.annotation) 43 | 44 | implementation(libs.commons.cli) 45 | implementation(libs.hiddenapibypass) 46 | } 47 | 48 | tasks.withType(JavaCompile).configureEach { 49 | options.compilerArgs << "-Xlint:deprecation" << "-Xlint:unchecked" 50 | } 51 | 52 | publishing { 53 | publications { 54 | mavenJava(MavenPublication) { 55 | group = rootProject.groupId 56 | artifactId = rootProject.artifactId 57 | version = rootProject.artifactVersion 58 | afterEvaluate { 59 | from components.findByName("release") 60 | } 61 | pom { 62 | name = rootProject.POM_NAME 63 | description = rootProject.POM_DESCRIPTION 64 | url = rootProject.POM_URL 65 | licenses { 66 | license { 67 | name = rootProject.POM_LICENCE_NAME 68 | url = rootProject.POM_LICENCE_URL 69 | } 70 | } 71 | developers { 72 | developer { 73 | name = rootProject.POM_DEVELOPER_NAME 74 | url = rootProject.POM_DEVELOPER_URL 75 | } 76 | } 77 | scm { 78 | connection = rootProject.POM_SCM_CONNECTION 79 | url = rootProject.POM_SCM_URL 80 | } 81 | } 82 | } 83 | } 84 | repositories { 85 | maven { 86 | name = "ossrh" 87 | url = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" 88 | credentials { 89 | username = ossrhUsername 90 | password = ossrhPassword 91 | } 92 | } 93 | } 94 | } 95 | 96 | signing { 97 | sign publishing.publications.mavenJava 98 | } 99 | -------------------------------------------------------------------------------- /app-process/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 | # Uncomment this to preserve the line number information for 9 | # debugging stack traces. 10 | -keepattributes SourceFile,LineNumberTable 11 | 12 | # If you keep the line number information, uncomment this to 13 | # hide the original source file name. 14 | -renamesourcefileattribute SourceFile 15 | -------------------------------------------------------------------------------- /app-process/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app-process/src/main/aidl/com/rosan/app_process/INewProcess.aidl: -------------------------------------------------------------------------------- 1 | package com.rosan.app_process; 2 | 3 | import com.rosan.app_process.IRemoteProcess; 4 | 5 | interface INewProcess { 6 | void exit(int code) = 1; 7 | 8 | // remote binder transact: 2 9 | 10 | IRemoteProcess remoteProcess(in List cmdList, in Map env, in String directory) = 3; 11 | } -------------------------------------------------------------------------------- /app-process/src/main/aidl/com/rosan/app_process/IRemoteProcess.aidl: -------------------------------------------------------------------------------- 1 | package com.rosan.app_process; 2 | 3 | interface IRemoteProcess { 4 | ParcelFileDescriptor getOutputStream(); 5 | 6 | ParcelFileDescriptor getInputStream(); 7 | 8 | ParcelFileDescriptor getErrorStream(); 9 | 10 | int exitValue(); 11 | 12 | void destroy(); 13 | 14 | int waitFor(); 15 | } -------------------------------------------------------------------------------- /app-process/src/main/java/com/rosan/app_process/AppProcess.java: -------------------------------------------------------------------------------- 1 | package com.rosan.app_process; 2 | 3 | import android.app.ActivityThread; 4 | import android.content.ComponentName; 5 | import android.content.Context; 6 | import android.os.Binder; 7 | import android.os.IBinder; 8 | import android.os.Parcel; 9 | import android.os.RemoteException; 10 | 11 | import androidx.annotation.NonNull; 12 | import androidx.annotation.Nullable; 13 | 14 | import java.io.Closeable; 15 | import java.io.File; 16 | import java.io.IOException; 17 | import java.io.PrintWriter; 18 | import java.util.ArrayList; 19 | import java.util.Arrays; 20 | import java.util.HashMap; 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.concurrent.Callable; 24 | 25 | public abstract class AppProcess implements Closeable { 26 | private Context mContext = null; 27 | 28 | private INewProcess mNewProcess = null; 29 | 30 | private final Map mChildProcess = new HashMap<>(); 31 | 32 | public static ProcessParams generateProcessParams(@NonNull String classPath, @NonNull String entryClassName, @NonNull List args) { 33 | List cmdList = new ArrayList<>(); 34 | cmdList.add("/system/bin/app_process"); 35 | cmdList.add("-Djava.class.path=" + classPath); 36 | cmdList.add("/system/bin"); 37 | cmdList.add(entryClassName); 38 | cmdList.addAll(args); 39 | return new ProcessParams(cmdList, null, null); 40 | } 41 | 42 | public static T binderWithCleanCallingIdentity(Callable action) throws Exception { 43 | final long callingIdentity = Binder.clearCallingIdentity(); 44 | try { 45 | return action.call(); 46 | } finally { 47 | Binder.restoreCallingIdentity(callingIdentity); 48 | } 49 | } 50 | 51 | public static IBinder binderWrapper(INewProcess newProcess, IBinder binder) { 52 | return new BinderWrapper(newProcess, binder); 53 | } 54 | 55 | public static boolean remoteTransact(INewProcess newProcess, IBinder binder, int code, Parcel data, Parcel reply, int flags) { 56 | IBinder processBinder = newProcess.asBinder(); 57 | Parcel processData = Parcel.obtain(); 58 | try { 59 | processData.writeInterfaceToken(processBinder.getInterfaceDescriptor()); 60 | processData.writeStrongBinder(binder); 61 | processData.writeInt(code); 62 | processData.writeInt(flags); 63 | processData.appendFrom(data, 0, data.dataSize()); 64 | return processBinder.transact(IBinder.FIRST_CALL_TRANSACTION + 2, processData, reply, 0); 65 | } catch (RemoteException e) { 66 | throw new RuntimeException(e); 67 | } finally { 68 | processData.recycle(); 69 | } 70 | } 71 | 72 | public @NonNull Process start(@NonNull String classPath, @NonNull String entryClassName, @NonNull List args) throws IOException { 73 | return startProcess(generateProcessParams(classPath, entryClassName, args)); 74 | } 75 | 76 | public @NonNull Process start(@NonNull String classPath, @NonNull String entryClassName, @NonNull String[] args) throws IOException { 77 | return start(classPath, entryClassName, Arrays.asList(args)); 78 | } 79 | 80 | public @NonNull Process start(@NonNull String classPath, @NonNull Class entryClass, @NonNull List args) throws IOException { 81 | return start(classPath, entryClass.getName(), args); 82 | } 83 | 84 | public @NonNull Process start(@NonNull String classPath, @NonNull Class entryClass, @NonNull String[] args) throws IOException { 85 | return start(classPath, entryClass, Arrays.asList(args)); 86 | } 87 | 88 | public boolean init() { 89 | return init(ActivityThread.currentActivityThread().getApplication()); 90 | } 91 | 92 | public synchronized boolean init(@NonNull Context context) { 93 | if (initialized()) return true; 94 | mContext = context; 95 | IBinder binder = startProcess(new ComponentName(mContext.getPackageName(), NewProcessImpl.class.getName())); 96 | if (binder == null) return false; 97 | mNewProcess = INewProcess.Stub.asInterface(binder); 98 | try { 99 | binder.linkToDeath(() -> { 100 | if (mNewProcess == null || mNewProcess.asBinder() != binder) return; 101 | mNewProcess = null; 102 | }, 0); 103 | } catch (RemoteException e) { 104 | e.printStackTrace(); 105 | } 106 | return initialized(); 107 | } 108 | 109 | public boolean initialized() { 110 | return mContext != null && mNewProcess != null && mNewProcess.asBinder().isBinderAlive(); 111 | } 112 | 113 | @Override 114 | public void close() { 115 | mContext = null; 116 | if (mNewProcess == null || !mNewProcess.asBinder().pingBinder()) return; 117 | try { 118 | mNewProcess.exit(0); 119 | } catch (RemoteException e) { 120 | e.printStackTrace(); 121 | } 122 | } 123 | 124 | protected abstract @NonNull Process newProcess(@NonNull ProcessParams params) throws IOException; 125 | 126 | private @NonNull Process startProcess(@NonNull ProcessParams params) throws IOException { 127 | if (!initialized()) return newProcess(params); 128 | return remoteProcess(params.getCmdList(), params.getEnv(), params.getDirectory()); 129 | } 130 | 131 | private @NonNull INewProcess requireNewProcess() { 132 | if (!initialized()) throw new IllegalStateException("please call init() first."); 133 | return mNewProcess; 134 | } 135 | 136 | public boolean remoteTransact(IBinder binder, int code, Parcel data, Parcel reply, int flags) { 137 | return remoteTransact(requireNewProcess(), binder, code, data, reply, flags); 138 | } 139 | 140 | public IBinder binderWrapper(IBinder binder) { 141 | return binderWrapper(requireNewProcess(), binder); 142 | } 143 | 144 | public Process remoteProcess(@NonNull List cmdList, @Nullable Map env, @Nullable String directory) { 145 | try { 146 | return new RemoteProcess(requireNewProcess().remoteProcess(cmdList, env, directory)); 147 | } catch (RemoteException e) { 148 | throw new RuntimeException(e); 149 | } 150 | } 151 | 152 | private final Map locks = new HashMap<>(); 153 | 154 | private synchronized Object buildLock(String token) { 155 | Object lock = locks.get(token); 156 | if (lock == null) lock = new Object(); 157 | locks.put(token, lock); 158 | return lock; 159 | } 160 | 161 | public IBinder startProcess(@NonNull ComponentName componentName, boolean useCache) { 162 | if (!useCache) startProcessUnchecked(componentName); 163 | return startProcess(componentName); 164 | } 165 | 166 | public IBinder startProcess(@NonNull ComponentName componentName) { 167 | String token = componentName.flattenToString(); 168 | synchronized (buildLock(token)) { 169 | IBinder existsBinder = mChildProcess.get(token); 170 | if (existsBinder != null) return existsBinder; 171 | final IBinder binder = startProcessUnchecked(componentName); 172 | if (binder == null) return null; 173 | mChildProcess.put(token, binder); 174 | try { 175 | binder.linkToDeath(() -> { 176 | IBinder curBinder = mChildProcess.get(token); 177 | if (curBinder == null || curBinder != binder) return; 178 | mChildProcess.remove(token); 179 | }, 0); 180 | } catch (RemoteException e) { 181 | e.printStackTrace(); 182 | } 183 | return binder; 184 | } 185 | } 186 | 187 | private IBinder startProcessUnchecked(@NonNull ComponentName componentName) { 188 | return NewProcessReceiver.start(mContext, this, componentName); 189 | } 190 | 191 | public static class Default extends AppProcess { 192 | @NonNull 193 | @Override 194 | protected Process newProcess(@NonNull ProcessParams params) throws IOException { 195 | List cmdList = params.getCmdList(); 196 | Map env = params.getEnv(); 197 | String directory = params.getDirectory(); 198 | ProcessBuilder builder = new ProcessBuilder().command(cmdList); 199 | if (directory != null) builder = builder.directory(new File(directory)); 200 | if (env != null) builder.environment().putAll(env); 201 | return builder.start(); 202 | } 203 | } 204 | 205 | public abstract static class Terminal extends Default { 206 | protected abstract @NonNull List newTerminal(); 207 | 208 | @NonNull 209 | @Override 210 | protected Process newProcess(@NonNull ProcessParams params) throws IOException { 211 | ProcessParams newParams = new ProcessParams(params).setCmdList(newTerminal()); 212 | Process process = super.newProcess(newParams); 213 | PrintWriter printWriter = new PrintWriter(process.getOutputStream(), true); 214 | int count = 0; 215 | StringBuilder buffer = new StringBuilder(); 216 | for (String element : params.getCmdList()) { 217 | if (++count > 1) buffer.append(" "); 218 | buffer.append(element); 219 | } 220 | printWriter.println(buffer); 221 | printWriter.println("exit $?"); 222 | return process; 223 | } 224 | } 225 | 226 | public static class Root extends Terminal { 227 | 228 | @NonNull 229 | @Override 230 | protected List newTerminal() { 231 | List terminal = new ArrayList<>(); 232 | terminal.add("su"); 233 | return terminal; 234 | } 235 | } 236 | 237 | public static class RootSystem extends Terminal { 238 | @NonNull 239 | @Override 240 | protected List newTerminal() { 241 | List terminal = new ArrayList<>(); 242 | terminal.add("su"); 243 | terminal.add("1000"); 244 | return terminal; 245 | } 246 | } 247 | 248 | public static class ProcessParams { 249 | private @NonNull List mCmdList; 250 | 251 | private @Nullable Map mEnv; 252 | 253 | private @Nullable String mDirectory; 254 | 255 | public ProcessParams(@NonNull List cmdList, @Nullable Map env, @Nullable String directory) { 256 | this.mCmdList = cmdList; 257 | this.mEnv = env; 258 | this.mDirectory = directory; 259 | } 260 | 261 | public ProcessParams(@NonNull ProcessParams params) { 262 | this.mCmdList = new ArrayList<>(params.getCmdList()); 263 | Map env = params.getEnv(); 264 | this.mEnv = env != null ? new HashMap<>(env) : null; 265 | this.mDirectory = params.getDirectory(); 266 | } 267 | 268 | @NonNull 269 | public List getCmdList() { 270 | return mCmdList; 271 | } 272 | 273 | @Nullable 274 | public Map getEnv() { 275 | return mEnv; 276 | } 277 | 278 | @Nullable 279 | public String getDirectory() { 280 | return mDirectory; 281 | } 282 | 283 | public ProcessParams setCmdList(@NonNull List mCmdList) { 284 | this.mCmdList = mCmdList; 285 | return this; 286 | } 287 | 288 | public ProcessParams setEnv(@Nullable Map mEnv) { 289 | this.mEnv = mEnv; 290 | return this; 291 | } 292 | 293 | public ProcessParams setDirectory(@Nullable String mDirectory) { 294 | this.mDirectory = mDirectory; 295 | return this; 296 | } 297 | } 298 | } -------------------------------------------------------------------------------- /app-process/src/main/java/com/rosan/app_process/BinderWrapper.java: -------------------------------------------------------------------------------- 1 | package com.rosan.app_process; 2 | 3 | import android.os.IBinder; 4 | import android.os.IInterface; 5 | import android.os.Parcel; 6 | import android.os.RemoteException; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | 11 | import java.io.FileDescriptor; 12 | 13 | class BinderWrapper implements IBinder { 14 | private final INewProcess mNewProcess; 15 | 16 | private final IBinder mBinder; 17 | 18 | BinderWrapper(@NonNull INewProcess newProcess, @NonNull IBinder binder) { 19 | this.mNewProcess = newProcess; 20 | this.mBinder = binder; 21 | } 22 | 23 | @Nullable 24 | @Override 25 | public String getInterfaceDescriptor() throws RemoteException { 26 | return mBinder.getInterfaceDescriptor(); 27 | } 28 | 29 | @Override 30 | public boolean pingBinder() { 31 | return mBinder.pingBinder(); 32 | } 33 | 34 | @Override 35 | public boolean isBinderAlive() { 36 | return mBinder.isBinderAlive(); 37 | } 38 | 39 | @Nullable 40 | @Override 41 | public IInterface queryLocalInterface(@NonNull String descriptor) { 42 | return null; 43 | } 44 | 45 | @Override 46 | public void dump(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException { 47 | mBinder.dump(fd, args); 48 | } 49 | 50 | @Override 51 | public void dumpAsync(@NonNull FileDescriptor fd, @Nullable String[] args) throws RemoteException { 52 | mBinder.dumpAsync(fd, args); 53 | } 54 | 55 | @Override 56 | public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException { 57 | return AppProcess.remoteTransact(mNewProcess, mBinder, code, data, reply, flags); 58 | } 59 | 60 | @Override 61 | public void linkToDeath(@NonNull DeathRecipient recipient, int flags) throws RemoteException { 62 | mBinder.linkToDeath(recipient, flags); 63 | } 64 | 65 | @Override 66 | public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) { 67 | return mBinder.unlinkToDeath(recipient, flags); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app-process/src/main/java/com/rosan/app_process/NewProcess.java: -------------------------------------------------------------------------------- 1 | package com.rosan.app_process; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.ActivityThread; 5 | import android.app.LoadedApk; 6 | import android.content.ComponentName; 7 | import android.content.Context; 8 | import android.content.ContextWrapper; 9 | import android.content.Intent; 10 | import android.content.pm.PackageManager; 11 | import android.os.Build; 12 | import android.os.Bundle; 13 | import android.os.IBinder; 14 | import android.os.IInterface; 15 | import android.os.Looper; 16 | import android.os.Process; 17 | import android.util.Log; 18 | 19 | import androidx.annotation.Keep; 20 | import androidx.annotation.NonNull; 21 | 22 | import org.apache.commons.cli.CommandLine; 23 | import org.apache.commons.cli.DefaultParser; 24 | import org.apache.commons.cli.Option; 25 | import org.apache.commons.cli.Options; 26 | import org.lsposed.hiddenapibypass.HiddenApiBypass; 27 | 28 | import java.lang.reflect.Constructor; 29 | import java.lang.reflect.InvocationTargetException; 30 | import java.lang.reflect.Method; 31 | import java.util.Arrays; 32 | import java.util.Collections; 33 | import java.util.List; 34 | import java.util.Objects; 35 | 36 | public class NewProcess { 37 | private static final String TAG = "NewProcess"; 38 | 39 | private static ActivityThread mActivityThread = null; 40 | 41 | @Keep 42 | public static void main(String[] args) throws Throwable { 43 | try { 44 | innerMain(args); 45 | } catch (Throwable e) { 46 | e.printStackTrace(); 47 | Log.e(TAG, "main", e); 48 | throw e; 49 | } 50 | } 51 | 52 | private static void innerMain(String[] args) throws Throwable { 53 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 54 | HiddenApiBypass.addHiddenApiExemptions(""); 55 | } 56 | Options options = new Options().addOption(Option.builder().longOpt("package").hasArg().required().type(String.class).build()).addOption(Option.builder().longOpt("token").hasArg().required().type(String.class).build()).addOption(Option.builder().longOpt("component").hasArg().required().type(String.class).build()); 57 | CommandLine cmdLine = new DefaultParser().parse(options, args); 58 | String packageName = cmdLine.getOptionValue("package"); 59 | String token = cmdLine.getOptionValue("token"); 60 | String component = cmdLine.getOptionValue("component"); 61 | ComponentName componentName = ComponentName.unflattenFromString(component); 62 | 63 | Bundle bundle = new Bundle(); 64 | IBinder binder = createBinder(componentName); 65 | bundle.putBinder(NewProcessReceiver.EXTRA_NEW_PROCESS, binder); 66 | bundle.putString(NewProcessReceiver.EXTRA_TOKEN, token); 67 | Intent intent = new Intent(NewProcessReceiver.ACTION_SEND_NEW_PROCESS) 68 | .setPackage(packageName) 69 | .putExtras(bundle); 70 | getSystemContext().sendBroadcast(intent); 71 | Looper.loop(); 72 | } 73 | 74 | public static IBinder createBinder(ComponentName componentName) throws PackageManager.NameNotFoundException, NoSuchFieldException, NoSuchMethodException, ClassNotFoundException { 75 | Context packageContext = getSystemContext().createPackageContext(componentName.getPackageName(), Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE); 76 | Class clazz = packageContext.getClassLoader().loadClass(componentName.getClassName()); 77 | Constructor constructor = null; 78 | try { 79 | constructor = clazz.getDeclaredConstructor(Context.class); 80 | } catch (NoSuchMethodException ignored) { 81 | } 82 | Object result; 83 | try { 84 | result = constructor != null ? constructor.newInstance(getUIDContext()) : clazz.newInstance(); 85 | } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { 86 | throw new RuntimeException(e); 87 | } 88 | return ((IInterface) result).asBinder(); 89 | } 90 | 91 | public static List getPackagesForUid(Context context, int uid) { 92 | String[] packageNames = context.getPackageManager().getPackagesForUid(uid); 93 | if (packageNames == null) return Collections.emptyList(); 94 | return Arrays.asList(packageNames); 95 | } 96 | 97 | private static @NonNull ActivityThread getActivityThread() { 98 | if (mActivityThread != null) return mActivityThread; 99 | if (Looper.getMainLooper() == null) Looper.prepareMainLooper(); 100 | if (Looper.myLooper() == null) Looper.prepare(); 101 | 102 | mActivityThread = ActivityThread.systemMain(); 103 | Objects.requireNonNull(mActivityThread); 104 | return getActivityThread(); 105 | } 106 | 107 | private static @NonNull Context getSystemContext() { 108 | return getActivityThread().getSystemContext(); 109 | } 110 | 111 | public static Context getUIDContext() throws PackageManager.NameNotFoundException, NoSuchFieldException, InvocationTargetException, NoSuchMethodException, IllegalAccessException { 112 | Context context = getSystemContext(); 113 | 114 | int uid = Process.myUid(); 115 | List packageNames = getPackagesForUid(context, uid); 116 | if (packageNames.isEmpty()) return context; 117 | if (packageNames.contains(context.getPackageName()) && (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q || packageNames.contains(context.getOpPackageName()))) 118 | return context; 119 | 120 | return createAppContext(context, packageNames.get(0)); 121 | } 122 | 123 | @SuppressLint("PrivateApi") 124 | private static Context createAppContext(Context context, String packageName) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, PackageManager.NameNotFoundException, NoSuchFieldException { 125 | Context impl = context.createPackageContext(packageName, Context.CONTEXT_IGNORE_SECURITY | Context.CONTEXT_INCLUDE_CODE); 126 | while (impl instanceof ContextWrapper) { 127 | impl = ((ContextWrapper) impl).getBaseContext(); 128 | } 129 | 130 | Method method = impl.getClass().getDeclaredMethod("createAppContext", ActivityThread.class, LoadedApk.class); 131 | method.setAccessible(true); 132 | return (Context) method.invoke(null, getActivityThread(), getActivityThread().peekPackageInfo(packageName, true)); 133 | } 134 | } -------------------------------------------------------------------------------- /app-process/src/main/java/com/rosan/app_process/NewProcessImpl.java: -------------------------------------------------------------------------------- 1 | package com.rosan.app_process; 2 | 3 | import android.os.Binder; 4 | import android.os.IBinder; 5 | import android.os.Parcel; 6 | import android.os.RemoteException; 7 | 8 | import androidx.annotation.Keep; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | public class NewProcessImpl extends INewProcess.Stub { 16 | @Keep 17 | public NewProcessImpl() { 18 | super(); 19 | } 20 | 21 | @Override 22 | public void exit(int code) { 23 | System.exit(code); 24 | } 25 | 26 | private boolean targetTransact(IBinder binder, int code, Parcel data, Parcel reply, int flags) throws RemoteException { 27 | try { 28 | return AppProcess.binderWithCleanCallingIdentity(() -> binder.transact(code, data, reply, flags)); 29 | } catch (RemoteException e) { 30 | throw e; 31 | } catch (Exception e) { 32 | throw new RuntimeException(e); 33 | } 34 | } 35 | 36 | @Override 37 | public IRemoteProcess remoteProcess(List cmdList, Map env, String directory) { 38 | ProcessBuilder builder = new ProcessBuilder().command(cmdList); 39 | if (directory != null) builder = builder.directory(new File(directory)); 40 | if (env != null) builder.environment().putAll(env); 41 | File file = null; 42 | try { 43 | return new RemoteProcessImpl(builder.start()); 44 | } catch (IOException e) { 45 | throw new IllegalStateException(e); 46 | } 47 | } 48 | 49 | @Override 50 | public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { 51 | if (code != Binder.FIRST_CALL_TRANSACTION + 2) 52 | return super.onTransact(code, data, reply, flags); 53 | Parcel targetData = Parcel.obtain(); 54 | try { 55 | data.enforceInterface(this.asBinder().getInterfaceDescriptor()); 56 | IBinder binder = data.readStrongBinder(); 57 | int targetCode = data.readInt(); 58 | int targetFlags = data.readInt(); 59 | targetData.appendFrom(data, data.dataPosition(), data.dataAvail()); 60 | return targetTransact(binder, targetCode, targetData, reply, targetFlags); 61 | } finally { 62 | targetData.recycle(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app-process/src/main/java/com/rosan/app_process/NewProcessReceiver.java: -------------------------------------------------------------------------------- 1 | package com.rosan.app_process; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.ComponentName; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.content.IntentFilter; 8 | import android.os.Build; 9 | import android.os.Bundle; 10 | import android.os.IBinder; 11 | 12 | import androidx.annotation.NonNull; 13 | 14 | import java.io.IOException; 15 | import java.util.Objects; 16 | import java.util.UUID; 17 | import java.util.concurrent.ExecutionException; 18 | import java.util.concurrent.ExecutorService; 19 | import java.util.concurrent.Executors; 20 | import java.util.concurrent.Future; 21 | import java.util.concurrent.LinkedBlockingQueue; 22 | import java.util.concurrent.TimeUnit; 23 | import java.util.concurrent.atomic.AtomicReference; 24 | 25 | abstract class NewProcessReceiver extends BroadcastReceiver { 26 | private static boolean waitFor(Process process, long timeout, TimeUnit unit) throws InterruptedException { 27 | long startTime = System.nanoTime(); 28 | long rem = unit.toNanos(timeout); 29 | 30 | do { 31 | try { 32 | process.exitValue(); 33 | return true; 34 | } catch (IllegalArgumentException ex) { 35 | if (rem > 0) 36 | Thread.sleep( 37 | Math.min(TimeUnit.NANOSECONDS.toMillis(rem) + 1, 100)); 38 | } 39 | rem = unit.toNanos(timeout) - (System.nanoTime() - startTime); 40 | } while (rem > 0); 41 | return false; 42 | } 43 | 44 | public static IBinder start(Context context, AppProcess appProcess, ComponentName componentName) { 45 | String token = UUID.randomUUID().toString(); 46 | IntentFilter filter = new IntentFilter(); 47 | filter.addAction(ACTION_SEND_NEW_PROCESS); 48 | LinkedBlockingQueue> queue = new LinkedBlockingQueue<>(); 49 | BroadcastReceiver receiver = new NewProcessReceiver() { 50 | @Override 51 | void onReceive(NewProcessResult result) { 52 | if (!result.getToken().equals(token)) return; 53 | queue.offer(new AtomicReference<>(result)); 54 | } 55 | }; 56 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) 57 | context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED); 58 | else context.registerReceiver(receiver, filter); 59 | ExecutorService executorService = Executors.newCachedThreadPool(); 60 | Future> future = executorService.submit(queue::take); 61 | try { 62 | Process process = appProcess.start(context.getPackageCodePath(), NewProcess.class, new String[]{ 63 | String.format("--package=%s", context.getPackageName()), 64 | String.format("--token=%s", token), 65 | String.format("--component=%s", componentName.flattenToString()) 66 | }); 67 | executorService.execute(() -> { 68 | try { 69 | waitFor(process, 15, TimeUnit.SECONDS); 70 | } catch (Throwable e) { 71 | e.printStackTrace(); 72 | } 73 | queue.offer(new AtomicReference<>()); 74 | }); 75 | NewProcessResult result = future.get().get(); 76 | IBinder binder = result != null ? result.getBinder() : null; 77 | if (binder == null) process.destroy(); 78 | return binder; 79 | } catch (IOException | ExecutionException | InterruptedException e) { 80 | e.printStackTrace(); 81 | return null; 82 | } finally { 83 | context.unregisterReceiver(receiver); 84 | executorService.shutdown(); 85 | } 86 | } 87 | 88 | public static String ACTION_SEND_NEW_PROCESS = "com.rosan.app_process.send.new_process"; 89 | 90 | public static String EXTRA_NEW_PROCESS = "new_process"; 91 | 92 | public static String EXTRA_TOKEN = "token"; 93 | 94 | @Override 95 | public void onReceive(Context context, Intent intent) { 96 | String action = intent.getAction(); 97 | Bundle extras = intent.getExtras(); 98 | if (extras == null) return; 99 | if (!Objects.equals(action, ACTION_SEND_NEW_PROCESS)) return; 100 | String token = extras.getString(EXTRA_TOKEN); 101 | if (token == null) return; 102 | IBinder binder = extras.getBinder(EXTRA_NEW_PROCESS); 103 | if (binder == null) return; 104 | onReceive(new NewProcessResult(token, binder)); 105 | } 106 | 107 | abstract void onReceive(NewProcessResult result); 108 | 109 | static class NewProcessResult { 110 | private final @NonNull String mToken; 111 | 112 | private final @NonNull IBinder mBinder; 113 | 114 | NewProcessResult(@NonNull String token, @NonNull IBinder binder) { 115 | mToken = token; 116 | mBinder = binder; 117 | } 118 | 119 | @NonNull 120 | public String getToken() { 121 | return mToken; 122 | } 123 | 124 | @NonNull 125 | public IBinder getBinder() { 126 | return mBinder; 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /app-process/src/main/java/com/rosan/app_process/RemoteProcess.java: -------------------------------------------------------------------------------- 1 | package com.rosan.app_process; 2 | 3 | import android.os.ParcelFileDescriptor; 4 | import android.os.RemoteException; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | 11 | public class RemoteProcess extends Process { 12 | private final @NonNull IRemoteProcess mProcess; 13 | 14 | public RemoteProcess(@NonNull IRemoteProcess process) { 15 | mProcess = process; 16 | } 17 | 18 | @Override 19 | public OutputStream getOutputStream() { 20 | try { 21 | return new ParcelFileDescriptor.AutoCloseOutputStream(mProcess.getOutputStream()); 22 | } catch (RemoteException e) { 23 | throw new RuntimeException(e); 24 | } 25 | } 26 | 27 | @Override 28 | public InputStream getInputStream() { 29 | try { 30 | return new ParcelFileDescriptor.AutoCloseInputStream(mProcess.getInputStream()); 31 | } catch (RemoteException e) { 32 | throw new RuntimeException(e); 33 | } 34 | } 35 | 36 | @Override 37 | public InputStream getErrorStream() { 38 | try { 39 | return new ParcelFileDescriptor.AutoCloseInputStream(mProcess.getErrorStream()); 40 | } catch (RemoteException e) { 41 | throw new RuntimeException(e); 42 | } 43 | } 44 | 45 | @Override 46 | public int waitFor() { 47 | try { 48 | return mProcess.waitFor(); 49 | } catch (RemoteException e) { 50 | throw new RuntimeException(e); 51 | } 52 | } 53 | 54 | @Override 55 | public int exitValue() { 56 | try { 57 | return mProcess.exitValue(); 58 | } catch (RemoteException e) { 59 | throw new RuntimeException(e); 60 | } 61 | } 62 | 63 | @Override 64 | public void destroy() { 65 | try { 66 | mProcess.destroy(); 67 | } catch (RemoteException e) { 68 | throw new RuntimeException(e); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app-process/src/main/java/com/rosan/app_process/RemoteProcessImpl.java: -------------------------------------------------------------------------------- 1 | package com.rosan.app_process; 2 | 3 | import android.os.ParcelFileDescriptor; 4 | import android.os.RemoteException; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | import java.io.IOException; 9 | import java.io.InputStream; 10 | import java.io.OutputStream; 11 | 12 | public class RemoteProcessImpl extends IRemoteProcess.Stub { 13 | private final @NonNull Process mProcess; 14 | 15 | private ParcelFileDescriptor mInputStream; 16 | 17 | private ParcelFileDescriptor mOutputStream; 18 | 19 | private ParcelFileDescriptor mErrorStream; 20 | 21 | public RemoteProcessImpl(@NonNull Process process) { 22 | mProcess = process; 23 | } 24 | 25 | @Override 26 | public ParcelFileDescriptor getOutputStream() { 27 | if (mOutputStream != null) return mOutputStream; 28 | try { 29 | mOutputStream = parcelable(mProcess.getOutputStream()); 30 | } catch (IOException e) { 31 | throw new IllegalStateException(e); 32 | } 33 | return mOutputStream; 34 | } 35 | 36 | @Override 37 | public ParcelFileDescriptor getInputStream() throws RemoteException { 38 | if (mInputStream != null) return mInputStream; 39 | try { 40 | mInputStream = parcelable(mProcess.getInputStream()); 41 | } catch (IOException e) { 42 | throw new IllegalStateException(e); 43 | } 44 | return mInputStream; 45 | } 46 | 47 | @Override 48 | public ParcelFileDescriptor getErrorStream() { 49 | if (mErrorStream != null) return mErrorStream; 50 | try { 51 | mErrorStream = parcelable(mProcess.getErrorStream()); 52 | } catch (IOException e) { 53 | throw new IllegalStateException(e); 54 | } 55 | return mErrorStream; 56 | } 57 | 58 | @Override 59 | public int exitValue() { 60 | return mProcess.exitValue(); 61 | } 62 | 63 | @Override 64 | public void destroy() { 65 | mProcess.destroy(); 66 | } 67 | 68 | @Override 69 | public int waitFor() { 70 | try { 71 | return mProcess.waitFor(); 72 | } catch (InterruptedException e) { 73 | throw new IllegalStateException(e); 74 | } 75 | } 76 | 77 | public static ParcelFileDescriptor parcelable(InputStream inputStream) throws IOException { 78 | ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 79 | transferThread(inputStream, new ParcelFileDescriptor.AutoCloseOutputStream(pipe[1])); 80 | return pipe[0]; 81 | } 82 | 83 | public static ParcelFileDescriptor parcelable(OutputStream outputStream) throws IOException { 84 | ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 85 | transferThread(new ParcelFileDescriptor.AutoCloseInputStream(pipe[0]), outputStream); 86 | return pipe[1]; 87 | } 88 | 89 | public static void transferThread(InputStream inputStream, OutputStream outputStream) { 90 | new Thread(() -> { 91 | byte[] bytes = new byte[8192]; 92 | int len = 0; 93 | try { 94 | while ((len = inputStream.read(bytes)) > 0) { 95 | outputStream.write(bytes, 0, len); 96 | outputStream.flush(); 97 | } 98 | } catch (IOException e) { 99 | e.printStackTrace(); 100 | } finally { 101 | try { 102 | inputStream.close(); 103 | } catch (IOException e) { 104 | e.printStackTrace(); 105 | } 106 | try { 107 | outputStream.close(); 108 | } catch (IOException e) { 109 | e.printStackTrace(); 110 | } 111 | } 112 | }).start(); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.agp.app) 3 | } 4 | 5 | android { 6 | namespace = "com.rosan.app_process.demo" 7 | compileSdk = 35 8 | 9 | defaultConfig { 10 | minSdk = 19 11 | targetSdk = 35 12 | versionCode = 1 13 | versionName = "1.0" 14 | multiDexEnabled = false 15 | proguardFiles += "proguard-rules.pro" 16 | } 17 | 18 | buildTypes { 19 | configureEach { 20 | signingConfig = signingConfigs.debug 21 | } 22 | release { 23 | minifyEnabled = true 24 | shrinkResources = true 25 | } 26 | } 27 | 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_21 30 | targetCompatibility JavaVersion.VERSION_21 31 | } 32 | } 33 | 34 | dependencies { 35 | implementation project(":app-process") 36 | } 37 | -------------------------------------------------------------------------------- /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 | # Uncomment this to preserve the line number information for 9 | # debugging stack traces. 10 | -keepattributes SourceFile,LineNumberTable 11 | 12 | # If you keep the line number information, uncomment this to 13 | # hide the original source file name. 14 | -renamesourcefileattribute SourceFile 15 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/rosan/app_process/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.rosan.app_process.demo; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | 6 | public class MainActivity extends Activity { 7 | 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | setContentView(R.layout.activity_main); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | alias(libs.plugins.agp.lib) apply false 4 | alias(libs.plugins.agp.app) apply false 5 | } 6 | 7 | ext { 8 | groupId = "io.github.iamr0s" 9 | artifactId = "AndroidAppProcess" 10 | artifactVersion = "1.3.1" 11 | POM_NAME = "Android app_process" 12 | POM_DESCRIPTION = "Run Android app_process just like Android Service." 13 | POM_URL = "https://github.com/iamr0s/AndroidAppProcess" 14 | POM_SCM_URL = "https://github.com/iamr0s/AndroidAppProcess" 15 | POM_SCM_CONNECTION = "scm:git@github.com:iamr0s/AndroidAppProcess.git" 16 | POM_SCM_DEV_CONNECTION = "scm:git@github.com:iamr0s/AndroidAppProcess.git" 17 | 18 | POM_LICENCE_NAME = "LGPL v3.0 License" 19 | POM_LICENCE_URL = "https://github.com/iamr0s/AndroidAppProcess/blob/main/LICENSE" 20 | POM_LICENCE_DIST = "repo" 21 | 22 | POM_DEVELOPER_ID = "iamr0s" 23 | POM_DEVELOPER_NAME = "iamr0s" 24 | POM_DEVELOPER_URL = "https://github.com/iamr0s" 25 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Duser.language=en -Dconsole.encoding=UTF-8 -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Enables namespacing of each library's R class so that its R class includes only the 19 | # resources declared in the library itself and none from the library's dependencies, 20 | # thereby reducing the size of the R class for that library 21 | android.nonTransitiveRClass=true 22 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | # Google Maven 2 | # https://maven.google.com/web/index.html?hl=zh-cn 3 | 4 | # Gradle Plugins 5 | # https://plugins.gradle.org/ 6 | 7 | # Maven 8 | # https://mvnrepository.com/ 9 | 10 | [versions] 11 | agp = "8.10.0" 12 | 13 | [plugins] 14 | agp-lib = { id = "com.android.library", version.ref = "agp" } 15 | agp-app = { id = "com.android.application", version.ref = "agp" } 16 | 17 | [libraries] 18 | annotation = { group = "androidx.annotation", name = "annotation", version = "1.9.1" } 19 | commons-cli = { group = "commons-cli", name = "commons-cli", version = "1.9.0" } 20 | hiddenapibypass = { group = "org.lsposed.hiddenapibypass", name = "hiddenapibypass", version = "6.1" } 21 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iamr0s/AndroidAppProcess/4f5d4e45469abac433ff87efbeddc9b22da64108/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /hidden-api/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.agp.lib) 3 | } 4 | 5 | android { 6 | namespace = "com.rosan.hidden_api" 7 | compileSdk = 35 8 | 9 | defaultConfig { 10 | minSdk = 19 11 | } 12 | 13 | compileOptions { 14 | sourceCompatibility JavaVersion.VERSION_21 15 | targetCompatibility JavaVersion.VERSION_21 16 | } 17 | } 18 | 19 | dependencies { 20 | compileOnly(libs.annotation) 21 | } 22 | 23 | tasks.withType(JavaCompile).configureEach { 24 | options.compilerArgs << "-Xlint:deprecation" << "-Xlint:unchecked" 25 | } 26 | -------------------------------------------------------------------------------- /hidden-api/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/app/ActivityManagerNative.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.os.IBinder; 4 | 5 | public class ActivityManagerNative { 6 | public static IActivityManager asInterface(IBinder binder) { 7 | throw new RuntimeException("Stub"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/app/ActivityThread.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | public class ActivityThread { 4 | public static ActivityThread currentActivityThread() { 5 | throw new RuntimeException("STUB"); 6 | } 7 | 8 | public static ActivityThread systemMain() { 9 | throw new RuntimeException("STUB"); 10 | } 11 | 12 | public ContextImpl getSystemContext() { 13 | throw new RuntimeException("STUB"); 14 | } 15 | 16 | public Application getApplication() { 17 | throw new RuntimeException("STUB"); 18 | } 19 | 20 | public final LoadedApk peekPackageInfo(String packageName, boolean includeCode) { 21 | throw new RuntimeException("STUB"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/app/ContentProviderHolder.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.content.IContentProvider; 4 | 5 | public class ContentProviderHolder { 6 | public IContentProvider provider; 7 | } 8 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/app/ContextImpl.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.ComponentName; 5 | import android.content.ContentResolver; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.content.IntentFilter; 9 | import android.content.IntentSender; 10 | import android.content.ServiceConnection; 11 | import android.content.SharedPreferences; 12 | import android.content.pm.ApplicationInfo; 13 | import android.content.pm.PackageManager; 14 | import android.content.res.AssetManager; 15 | import android.content.res.Configuration; 16 | import android.content.res.Resources; 17 | import android.database.DatabaseErrorHandler; 18 | import android.database.sqlite.SQLiteDatabase; 19 | import android.graphics.Bitmap; 20 | import android.graphics.drawable.Drawable; 21 | import android.net.Uri; 22 | import android.os.Bundle; 23 | import android.os.Handler; 24 | import android.os.Looper; 25 | import android.os.UserHandle; 26 | import android.view.Display; 27 | 28 | import androidx.annotation.NonNull; 29 | import androidx.annotation.Nullable; 30 | 31 | import java.io.File; 32 | import java.io.FileInputStream; 33 | import java.io.FileNotFoundException; 34 | import java.io.FileOutputStream; 35 | import java.io.IOException; 36 | import java.io.InputStream; 37 | 38 | public class ContextImpl extends Context { 39 | @Override 40 | public AssetManager getAssets() { 41 | return null; 42 | } 43 | 44 | @Override 45 | public Resources getResources() { 46 | return null; 47 | } 48 | 49 | @Override 50 | public PackageManager getPackageManager() { 51 | return null; 52 | } 53 | 54 | @Override 55 | public ContentResolver getContentResolver() { 56 | return null; 57 | } 58 | 59 | @Override 60 | public Looper getMainLooper() { 61 | return null; 62 | } 63 | 64 | @Override 65 | public Context getApplicationContext() { 66 | return null; 67 | } 68 | 69 | @Override 70 | public void setTheme(int resid) { 71 | 72 | } 73 | 74 | @Override 75 | public Resources.Theme getTheme() { 76 | return null; 77 | } 78 | 79 | @Override 80 | public ClassLoader getClassLoader() { 81 | return null; 82 | } 83 | 84 | @Override 85 | public String getPackageName() { 86 | return null; 87 | } 88 | 89 | @Override 90 | public ApplicationInfo getApplicationInfo() { 91 | return null; 92 | } 93 | 94 | @Override 95 | public String getPackageResourcePath() { 96 | return null; 97 | } 98 | 99 | @Override 100 | public String getPackageCodePath() { 101 | return null; 102 | } 103 | 104 | @Override 105 | public SharedPreferences getSharedPreferences(String name, int mode) { 106 | return null; 107 | } 108 | 109 | @Override 110 | public boolean moveSharedPreferencesFrom(Context sourceContext, String name) { 111 | return false; 112 | } 113 | 114 | @Override 115 | public boolean deleteSharedPreferences(String name) { 116 | return false; 117 | } 118 | 119 | @Override 120 | public FileInputStream openFileInput(String name) throws FileNotFoundException { 121 | return null; 122 | } 123 | 124 | @Override 125 | public FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException { 126 | return null; 127 | } 128 | 129 | @Override 130 | public boolean deleteFile(String name) { 131 | return false; 132 | } 133 | 134 | @Override 135 | public File getFileStreamPath(String name) { 136 | return null; 137 | } 138 | 139 | @Override 140 | public File getDataDir() { 141 | return null; 142 | } 143 | 144 | @Override 145 | public File getFilesDir() { 146 | return null; 147 | } 148 | 149 | @Override 150 | public File getNoBackupFilesDir() { 151 | return null; 152 | } 153 | 154 | @Nullable 155 | @Override 156 | public File getExternalFilesDir(@Nullable String type) { 157 | return null; 158 | } 159 | 160 | @Override 161 | public File[] getExternalFilesDirs(String type) { 162 | return new File[0]; 163 | } 164 | 165 | @Override 166 | public File getObbDir() { 167 | return null; 168 | } 169 | 170 | @Override 171 | public File[] getObbDirs() { 172 | return new File[0]; 173 | } 174 | 175 | @Override 176 | public File getCacheDir() { 177 | return null; 178 | } 179 | 180 | @Override 181 | public File getCodeCacheDir() { 182 | return null; 183 | } 184 | 185 | @Nullable 186 | @Override 187 | public File getExternalCacheDir() { 188 | return null; 189 | } 190 | 191 | @Override 192 | public File[] getExternalCacheDirs() { 193 | return new File[0]; 194 | } 195 | 196 | @Override 197 | public File[] getExternalMediaDirs() { 198 | return new File[0]; 199 | } 200 | 201 | @Override 202 | public String[] fileList() { 203 | return new String[0]; 204 | } 205 | 206 | @Override 207 | public File getDir(String name, int mode) { 208 | return null; 209 | } 210 | 211 | @Override 212 | public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) { 213 | return null; 214 | } 215 | 216 | @Override 217 | public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, @Nullable DatabaseErrorHandler errorHandler) { 218 | return null; 219 | } 220 | 221 | @Override 222 | public boolean moveDatabaseFrom(Context sourceContext, String name) { 223 | return false; 224 | } 225 | 226 | @Override 227 | public boolean deleteDatabase(String name) { 228 | return false; 229 | } 230 | 231 | @Override 232 | public File getDatabasePath(String name) { 233 | return null; 234 | } 235 | 236 | @Override 237 | public String[] databaseList() { 238 | return new String[0]; 239 | } 240 | 241 | @Override 242 | public Drawable getWallpaper() { 243 | return null; 244 | } 245 | 246 | @Override 247 | public Drawable peekWallpaper() { 248 | return null; 249 | } 250 | 251 | @Override 252 | public int getWallpaperDesiredMinimumWidth() { 253 | return 0; 254 | } 255 | 256 | @Override 257 | public int getWallpaperDesiredMinimumHeight() { 258 | return 0; 259 | } 260 | 261 | @Override 262 | public void setWallpaper(Bitmap bitmap) throws IOException { 263 | 264 | } 265 | 266 | @Override 267 | public void setWallpaper(InputStream data) throws IOException { 268 | 269 | } 270 | 271 | @Override 272 | public void clearWallpaper() throws IOException { 273 | 274 | } 275 | 276 | @Override 277 | public void startActivity(Intent intent) { 278 | 279 | } 280 | 281 | @Override 282 | public void startActivity(Intent intent, @Nullable Bundle options) { 283 | 284 | } 285 | 286 | @Override 287 | public void startActivities(Intent[] intents) { 288 | 289 | } 290 | 291 | @Override 292 | public void startActivities(Intent[] intents, Bundle options) { 293 | 294 | } 295 | 296 | @Override 297 | public void startIntentSender(IntentSender intent, @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws IntentSender.SendIntentException { 298 | 299 | } 300 | 301 | @Override 302 | public void startIntentSender(IntentSender intent, @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, @Nullable Bundle options) throws IntentSender.SendIntentException { 303 | 304 | } 305 | 306 | @Override 307 | public void sendBroadcast(Intent intent) { 308 | 309 | } 310 | 311 | @Override 312 | public void sendBroadcast(Intent intent, @Nullable String receiverPermission) { 313 | 314 | } 315 | 316 | @Override 317 | public void sendOrderedBroadcast(Intent intent, @Nullable String receiverPermission) { 318 | 319 | } 320 | 321 | @Override 322 | public void sendOrderedBroadcast(@NonNull Intent intent, @Nullable String receiverPermission, @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler, int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) { 323 | 324 | } 325 | 326 | @Override 327 | public void sendBroadcastAsUser(Intent intent, UserHandle user) { 328 | 329 | } 330 | 331 | @Override 332 | public void sendBroadcastAsUser(Intent intent, UserHandle user, @Nullable String receiverPermission) { 333 | 334 | } 335 | 336 | @Override 337 | public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, @Nullable String receiverPermission, BroadcastReceiver resultReceiver, @Nullable Handler scheduler, int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) { 338 | 339 | } 340 | 341 | @Override 342 | public void sendStickyBroadcast(Intent intent) { 343 | 344 | } 345 | 346 | @Override 347 | public void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver, @Nullable Handler scheduler, int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) { 348 | 349 | } 350 | 351 | @Override 352 | public void removeStickyBroadcast(Intent intent) { 353 | 354 | } 355 | 356 | @Override 357 | public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) { 358 | 359 | } 360 | 361 | @Override 362 | public void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle user, BroadcastReceiver resultReceiver, @Nullable Handler scheduler, int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) { 363 | 364 | } 365 | 366 | @Override 367 | public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) { 368 | 369 | } 370 | 371 | @Nullable 372 | @Override 373 | public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter) { 374 | return null; 375 | } 376 | 377 | @Nullable 378 | @Override 379 | public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter, int flags) { 380 | return null; 381 | } 382 | 383 | @Nullable 384 | @Override 385 | public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, @Nullable String broadcastPermission, @Nullable Handler scheduler) { 386 | return null; 387 | } 388 | 389 | @Nullable 390 | @Override 391 | public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, @Nullable String broadcastPermission, @Nullable Handler scheduler, int flags) { 392 | return null; 393 | } 394 | 395 | @Override 396 | public void unregisterReceiver(BroadcastReceiver receiver) { 397 | 398 | } 399 | 400 | @Nullable 401 | @Override 402 | public ComponentName startService(Intent service) { 403 | return null; 404 | } 405 | 406 | @Nullable 407 | @Override 408 | public ComponentName startForegroundService(Intent service) { 409 | return null; 410 | } 411 | 412 | @Override 413 | public boolean stopService(Intent service) { 414 | return false; 415 | } 416 | 417 | @Override 418 | public boolean bindService(Intent service, @NonNull ServiceConnection conn, int flags) { 419 | return false; 420 | } 421 | 422 | @Override 423 | public void unbindService(@NonNull ServiceConnection conn) { 424 | 425 | } 426 | 427 | @Override 428 | public boolean startInstrumentation(@NonNull ComponentName className, @Nullable String profileFile, @Nullable Bundle arguments) { 429 | return false; 430 | } 431 | 432 | @Override 433 | public Object getSystemService(@NonNull String name) { 434 | return null; 435 | } 436 | 437 | @Nullable 438 | @Override 439 | public String getSystemServiceName(@NonNull Class serviceClass) { 440 | return null; 441 | } 442 | 443 | @Override 444 | public int checkPermission(@NonNull String permission, int pid, int uid) { 445 | return 0; 446 | } 447 | 448 | @Override 449 | public int checkCallingPermission(@NonNull String permission) { 450 | return 0; 451 | } 452 | 453 | @Override 454 | public int checkCallingOrSelfPermission(@NonNull String permission) { 455 | return 0; 456 | } 457 | 458 | @Override 459 | public int checkSelfPermission(@NonNull String permission) { 460 | return 0; 461 | } 462 | 463 | @Override 464 | public void enforcePermission(@NonNull String permission, int pid, int uid, @Nullable String message) { 465 | 466 | } 467 | 468 | @Override 469 | public void enforceCallingPermission(@NonNull String permission, @Nullable String message) { 470 | 471 | } 472 | 473 | @Override 474 | public void enforceCallingOrSelfPermission(@NonNull String permission, @Nullable String message) { 475 | 476 | } 477 | 478 | @Override 479 | public void grantUriPermission(String toPackage, Uri uri, int modeFlags) { 480 | 481 | } 482 | 483 | @Override 484 | public void revokeUriPermission(Uri uri, int modeFlags) { 485 | 486 | } 487 | 488 | @Override 489 | public void revokeUriPermission(String toPackage, Uri uri, int modeFlags) { 490 | 491 | } 492 | 493 | @Override 494 | public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) { 495 | return 0; 496 | } 497 | 498 | @Override 499 | public int checkCallingUriPermission(Uri uri, int modeFlags) { 500 | return 0; 501 | } 502 | 503 | @Override 504 | public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) { 505 | return 0; 506 | } 507 | 508 | @Override 509 | public int checkUriPermission(@Nullable Uri uri, @Nullable String readPermission, @Nullable String writePermission, int pid, int uid, int modeFlags) { 510 | return 0; 511 | } 512 | 513 | @Override 514 | public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message) { 515 | 516 | } 517 | 518 | @Override 519 | public void enforceCallingUriPermission(Uri uri, int modeFlags, String message) { 520 | 521 | } 522 | 523 | @Override 524 | public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message) { 525 | 526 | } 527 | 528 | @Override 529 | public void enforceUriPermission(@Nullable Uri uri, @Nullable String readPermission, @Nullable String writePermission, int pid, int uid, int modeFlags, @Nullable String message) { 530 | 531 | } 532 | 533 | @Override 534 | public Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException { 535 | return null; 536 | } 537 | 538 | @Override 539 | public Context createContextForSplit(String splitName) throws PackageManager.NameNotFoundException { 540 | return null; 541 | } 542 | 543 | @Override 544 | public Context createConfigurationContext(@NonNull Configuration overrideConfiguration) { 545 | return null; 546 | } 547 | 548 | @Override 549 | public Context createDisplayContext(@NonNull Display display) { 550 | return null; 551 | } 552 | 553 | @Override 554 | public Context createDeviceProtectedStorageContext() { 555 | return null; 556 | } 557 | 558 | @Override 559 | public boolean isDeviceProtectedStorage() { 560 | return false; 561 | } 562 | } 563 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/app/IActivityManager.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | import android.os.Binder; 4 | import android.os.Build; 5 | import android.os.IBinder; 6 | import android.os.IInterface; 7 | import android.os.RemoteException; 8 | 9 | import androidx.annotation.RequiresApi; 10 | 11 | public interface IActivityManager extends IInterface { 12 | @RequiresApi(Build.VERSION_CODES.Q) 13 | ContentProviderHolder getContentProviderExternal(String name, int userId, IBinder token, String tag) 14 | throws RemoteException; 15 | 16 | ContentProviderHolder getContentProviderExternal(String name, int userId, IBinder token) 17 | throws RemoteException; 18 | 19 | abstract class Stub extends Binder implements IActivityManager { 20 | public static IActivityManager asInterface(IBinder binder) { 21 | throw new RuntimeException("Stub"); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/app/LoadedApk.java: -------------------------------------------------------------------------------- 1 | package android.app; 2 | 3 | public final class LoadedApk { 4 | } 5 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/content/IContentProvider.java: -------------------------------------------------------------------------------- 1 | package android.content; 2 | 3 | import android.os.IInterface; 4 | 5 | public interface IContentProvider extends IInterface { 6 | } 7 | -------------------------------------------------------------------------------- /hidden-api/src/main/java/android/os/ServiceManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2009 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package android.os; 18 | 19 | import java.util.Map; 20 | 21 | public final class ServiceManager { 22 | 23 | /** 24 | * Returns a reference to a service with the given name. 25 | * 26 | * @param name the name of the service to get 27 | * @return a reference to the service, or null if the service doesn't exist 28 | */ 29 | public static IBinder getService(String name) { 30 | return null; 31 | } 32 | 33 | /** 34 | * Is not supposed to return null, but that is fine for layoutlib. 35 | */ 36 | public static IBinder getServiceOrThrow(String name) throws ServiceNotFoundException { 37 | throw new ServiceNotFoundException(name); 38 | } 39 | 40 | /** 41 | * Place a new @a service called @a name into the service 42 | * manager. 43 | * 44 | * @param name the name of the new service 45 | * @param service the service object 46 | */ 47 | public static void addService(String name, IBinder service) { 48 | // pass 49 | } 50 | 51 | /** 52 | * Retrieve an existing service called @a name from the 53 | * service manager. Non-blocking. 54 | */ 55 | public static IBinder checkService(String name) { 56 | return null; 57 | } 58 | 59 | /** 60 | * Return a list of all currently running services. 61 | * @return an array of all currently running services, or null in 62 | * case of an exception 63 | */ 64 | public static String[] listServices() { 65 | // actual implementation returns null sometimes, so it's ok 66 | // to return null instead of an empty list. 67 | return null; 68 | } 69 | 70 | /** 71 | * This is only intended to be called when the process is first being brought 72 | * up and bound by the activity manager. There is only one thread in the process 73 | * at that time, so no locking is done. 74 | * 75 | * @param cache the cache of service references 76 | * @hide 77 | */ 78 | public static void initServiceCache(Map cache) { 79 | // pass 80 | } 81 | 82 | /** 83 | * Exception thrown when no service published for given name. This might be 84 | * thrown early during boot before certain services have published 85 | * themselves. 86 | * 87 | * @hide 88 | */ 89 | public static class ServiceNotFoundException extends Exception { 90 | // identical to the original implementation 91 | public ServiceNotFoundException(String name) { 92 | super("No service published for: " + name); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | 9 | dependencyResolutionManagement { 10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 11 | repositories { 12 | google() 13 | mavenCentral() 14 | } 15 | } 16 | 17 | rootProject.name = "App-Process" 18 | include ":app-process", ":hidden-api", ":app" 19 | --------------------------------------------------------------------------------