diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..47ead5d --- /dev/null +++ b/.gitattributes @@ -0,0 +1,43 @@ +# Java sources +*.java text diff=java +*.kt text diff=kotlin +*.groovy text diff=java +*.scala text diff=java +*.gradle text diff=java +*.gradle.kts text diff=kotlin + +# These files are text and should be normalized (Convert crlf => lf) +*.css text diff=css +*.scss text diff=css +*.sass text +*.df text +*.htm text diff=html +*.html text diff=html +*.js text +*.mjs text +*.cjs text +*.jsp text +*.jspf text +*.jspx text +*.properties text +*.tld text +*.tag text +*.tagx text +*.xml text + +# These files are binary and should be left untouched +# (binary is a macro for -text -diff) +*.class binary +*.dll binary +*.ear binary +*.jar binary +*.so binary +*.war binary +*.jks binary + +# Common build-tool wrapper scripts ('.cmd' versions are handled by 'Common.gitattributes') +mvnw text eol=lf +gradlew text eol=lf + +# These are explicitly windows files and should use crlf +*.bat text eol=crlf diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 3d1807c..2395d98 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -2,6 +2,8 @@ name: Android CI on: push: + branches: + - main pull_request: workflow_dispatch: @@ -11,51 +13,41 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Checkout libxposed/api - uses: actions/checkout@v6 - with: - repository: libxposed/api - path: libxposed/api - fetch-depth: 0 - - - name: set up JDK 21 - uses: actions/setup-java@v5 - with: - java-version: '21' - distribution: 'temurin' - cache: gradle - - - name: Write key - if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' - run: | - if [ ! -z "${{ secrets.SIGNING_KEY }}" ]; then - echo storePassword='${{ secrets.KEY_STORE_PASSWORD }}' >> local.properties - echo keyAlias='${{ secrets.ALIAS }}' >> local.properties - echo keyPassword='${{ secrets.KEY_PASSWORD }}' >> local.properties - echo storeFile='key.jks' >> local.properties - echo ${{ secrets.SIGNING_KEY }} | base64 --decode > key.jks - fi - - - name: Build with Gradle - run: | - # gradle properties - mkdir -p ~/.gradle - echo 'org.gradle.caching=true' >> ~/.gradle/gradle.properties - echo 'org.gradle.parallel=true' >> ~/.gradle/gradle.properties - echo 'org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -XX:+UseParallelGC' >> ~/.gradle/gradle.properties - echo 'android.native.buildOutput=verbose' >> ~/.gradle/gradle.properties - # build dependencies - cd libxposed/api && ./gradlew publishToMavenLocal && cd ../.. - # build DisableFlagSecure - chmod +x gradlew - ./gradlew assembleRelease assembleDebug - - - name: Upload artifact - uses: actions/upload-artifact@v6 - with: - name: Signed app bundle - path: app/build/outputs/apk + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + + - name: set up JDK 21 + uses: actions/setup-java@v5 + with: + java-version: '21' + distribution: 'temurin' + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v6 + + - name: Write key + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main' + run: | + if [ ! -z "${{ secrets.SIGNING_KEY }}" ]; then + echo storePassword='${{ secrets.KEY_STORE_PASSWORD }}' >> local.properties + echo keyAlias='${{ secrets.ALIAS }}' >> local.properties + echo keyPassword='${{ secrets.KEY_PASSWORD }}' >> local.properties + echo storeFile='key.jks' >> local.properties + echo ${{ secrets.SIGNING_KEY }} | base64 --decode > key.jks + fi + + - name: Build with Gradle + run: ./gradlew assemble + + - name: Upload release artifact + uses: actions/upload-artifact@v7 + with: + path: app/build/outputs/apk/release/*.apk + archive: false + + - name: Upload debug artifact + uses: actions/upload-artifact@v7 + with: + path: app/build/outputs/apk/debug/*.apk + archive: false diff --git a/app/build.gradle b/app/build.gradle index 3b39dca..60f9098 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,7 +4,7 @@ plugins { android { compileSdk { - version = release(36) { + version = release(37) { minorApiLevel = 1 } } @@ -13,7 +13,7 @@ android { defaultConfig { applicationId "io.github.lsposed.disableflagsecure" minSdkVersion 31 - targetSdkVersion 36 + targetSdkVersion 37 versionCode rootProject.ext.commitCount versionName "5.0.1" } @@ -70,6 +70,6 @@ android { } dependencies { - compileOnly 'androidx.annotation:annotation:1.9.1' - compileOnly 'io.github.libxposed:api:101.0.0' + compileOnly 'androidx.annotation:annotation:1.10.0' + compileOnly 'io.github.libxposed:api:102.0.0' } diff --git a/app/src/main/java/io/github/lsposed/disableflagsecure/DisableFlagSecure.java b/app/src/main/java/io/github/lsposed/disableflagsecure/DisableFlagSecure.java index 90760e1..c16fddf 100644 --- a/app/src/main/java/io/github/lsposed/disableflagsecure/DisableFlagSecure.java +++ b/app/src/main/java/io/github/lsposed/disableflagsecure/DisableFlagSecure.java @@ -6,12 +6,16 @@ import android.hardware.display.DisplayManager; import android.os.Build; import android.util.Log; +import android.util.Pair; import android.view.SurfaceControl; import androidx.annotation.NonNull; import androidx.annotation.RequiresApi; +import java.lang.reflect.Executable; import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; import java.util.function.BiConsumer; import java.util.function.BiPredicate; @@ -27,6 +31,8 @@ public class DisableFlagSecure extends XposedModule { private static final String MIUI_SCREENSHOT = "com.miui.screenshot"; private static XposedModule module; + private Pair param; + private final Set hookedIds = new HashSet<>(); @Override public void onModuleLoaded(@NonNull ModuleLoadedParam param) { @@ -36,13 +42,17 @@ public void onModuleLoaded(@NonNull ModuleLoadedParam param) { @Override public void onSystemServerStarting(@NonNull SystemServerStartingParam param) { var classLoader = param.getClassLoader(); - + this.param = Pair.create("system", classLoader); try { deoptimizeSystemServer(classLoader); } catch (Throwable t) { log(Log.ERROR, TAG, "deoptimize system server failed", t); } + hookSystemServer(classLoader); + } + + private void hookSystemServer(ClassLoader classLoader) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { // Screen record detection (V~Baklava) try { @@ -141,8 +151,13 @@ public void onPackageReady(@NonNull PackageReadyParam param) { if (!param.isFirstPackage()) return; var classLoader = param.getClassLoader(); - var pn = param.getPackageName(); - switch (pn) { + var packageName = param.getPackageName(); + this.param = Pair.create(packageName, classLoader); + hookPackage(packageName, classLoader); + } + + private void hookPackage(String packageName, ClassLoader classLoader) { + switch (packageName) { case OPLUS_SCREENSHOT: // Oplus Screenshot 15.0.0 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { @@ -167,7 +182,7 @@ public void onPackageReady(@NonNull PackageReadyParam param) { } case SYSTEMUI: case MIUI_SCREENSHOT: - if (OPLUS_APPPLATFORM.equals(pn) || OPLUS_SCREENSHOT.equals(pn) || + if (OPLUS_APPPLATFORM.equals(packageName) || OPLUS_SCREENSHOT.equals(packageName) || Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // ScreenCapture in App (S~T) (OPlus S~V) // TODO: test Oplus Baklava @@ -186,6 +201,36 @@ public void onPackageReady(@NonNull PackageReadyParam param) { } } + @Override + public boolean onHotReloading(@NonNull HotReloadingParam param) { + param.setSavedInstanceState(this.param); + return true; + } + + @Override + public void onHotReloaded(@NonNull HotReloadedParam param) { + var isSystemServer = param.isSystemServer(); + if (param.getSavedInstanceState() instanceof Pair pair + && pair.first instanceof String packageName + && pair.second instanceof ClassLoader classLoader) { + this.param = Pair.create(packageName, classLoader); + try { + if (isSystemServer) { + hookSystemServer(classLoader); + } else { + hookPackage(packageName, classLoader); + } + } catch (Throwable tr) { + log(Log.ERROR, TAG, "Hot reload failed", tr); + } + } + param.getOldHookHandles().forEach(h -> { + if (!hookedIds.contains(h.getId())) { + h.unhook(); + } + }); + } + private void deoptimizeSystemServer(ClassLoader classLoader) throws ClassNotFoundException { deoptimizeMethods( classLoader.loadClass("com.android.server.wm.WindowStateAnimator"), @@ -224,7 +269,7 @@ private void hookWindowState(ClassLoader classLoader) throws ClassNotFoundExcept var windowStateClazz = classLoader.loadClass("com.android.server.wm.WindowState"); var systemServerCl = windowStateClazz.getClassLoader(); var isSecureLockedMethod = windowStateClazz.getDeclaredMethod("isSecureLocked"); - hook(isSecureLockedMethod).intercept(chain -> { + hookE(isSecureLockedMethod).intercept(chain -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { var walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); var match = walker.walk(frames -> frames @@ -294,7 +339,7 @@ private void hookDisplayControl(ClassLoader classLoader) throws ClassNotFoundExc Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM ? "createVirtualDisplay" : "createDisplay", String.class, boolean.class); - hook(method).intercept(chain -> { + hookE(method).intercept(chain -> { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { var stackTrace = new Throwable().getStackTrace(); for (var frame : stackTrace) { @@ -342,7 +387,7 @@ private void hookActivityTaskManagerService(ClassLoader classLoader) throws Clas var iBinderClazz = classLoader.loadClass("android.os.IBinder"); var iScreenCaptureObserverClazz = classLoader.loadClass("android.app.IScreenCaptureObserver"); var method = activityTaskManagerServiceClazz.getDeclaredMethod("registerScreenCaptureObserver", iBinderClazz, iScreenCaptureObserverClazz); - hook(method).intercept(chain -> null); + hookE(method).intercept(chain -> null); } @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) @@ -350,13 +395,13 @@ private void hookWindowManagerService(ClassLoader classLoader) throws ClassNotFo var windowManagerServiceClazz = classLoader.loadClass("com.android.server.wm.WindowManagerService"); var iScreenRecordingCallbackClazz = classLoader.loadClass("android.window.IScreenRecordingCallback"); var method = windowManagerServiceClazz.getDeclaredMethod("registerScreenRecordingCallback", iScreenRecordingCallbackClazz); - hook(method).intercept(chain -> false); + hookE(method).intercept(chain -> false); } private void hookActivityManagerService(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException { var activityTaskManagerServiceClazz = classLoader.loadClass("com.android.server.am.ActivityManagerService"); var method = activityTaskManagerServiceClazz.getDeclaredMethod("checkPermission", String.class, int.class, int.class); - hook(method).intercept(chain -> { + hookE(method).intercept(chain -> { var permission = chain.getArg(0); if ("android.permission.CAPTURE_BLACKOUT_CONTENT".equals(permission)) { var args = chain.getArgs().toArray(); @@ -379,14 +424,14 @@ private void hookScreenshotHardwareBuffer(ClassLoader classLoader) throws ClassN "android.window.ScreenCapture$ScreenshotHardwareBuffer" : "android.view.SurfaceControl$ScreenshotHardwareBuffer"); var method = screenshotHardwareBufferClazz.getDeclaredMethod("containsSecureLayers"); - hook(method).intercept(chain -> false); + hookE(method).intercept(chain -> false); } @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) private void hookOplusScreenCapture(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException { var oplusScreenCaptureClazz = classLoader.loadClass("com.oplus.screenshot.OplusScreenCapture$CaptureArgs$Builder"); var method = oplusScreenCaptureClazz.getDeclaredMethod("setUid", long.class); - hook(method).intercept(chain -> { + hookE(method).intercept(chain -> { var args = chain.getArgs().toArray(); args[0] = -1; return chain.proceed(args); @@ -408,12 +453,24 @@ private void hookMethods(Class clazz, Hooker hooker, String... names) { var list = Arrays.asList(names); Arrays.stream(clazz.getDeclaredMethods()) .filter(method -> list.contains(method.getName())) - .forEach(method -> hook(method).intercept(hooker)); + .forEach(method -> hookE(method).intercept(hooker)); + } + + private HookBuilder hookE(Executable executable) { + var builder = hook(executable); + + if (getApiVersion() >= 102) { + var id = executable.toGenericString(); + builder.setId(id); + hookedIds.add(id); + } + + return builder; } private void hookOnResume() throws NoSuchMethodException { var method = Activity.class.getDeclaredMethod("onResume"); - hook(method).intercept(chain -> { + hookE(method).intercept(chain -> { var activity = (Activity) chain.getThisObject(); new AlertDialog.Builder(activity) .setTitle("Enable Screenshot") diff --git a/app/src/main/resources/META-INF/xposed/module.prop b/app/src/main/resources/META-INF/xposed/module.prop index dba303d..9b84a4d 100644 --- a/app/src/main/resources/META-INF/xposed/module.prop +++ b/app/src/main/resources/META-INF/xposed/module.prop @@ -1,3 +1,4 @@ minApiVersion=101 -targetApiVersion=101 -staticScope=true \ No newline at end of file +targetApiVersion=102 +staticScope=true +autoHotReload=true \ No newline at end of file diff --git a/build.gradle b/build.gradle index fcdb8d2..d07b1b8 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'com.android.application' version '9.1.0' apply false + id 'com.android.application' version '9.3.1' apply false id 'org.lsposed.lsplugin.jgit' version "1.1" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 61285a6..eddabd2 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index dbc3ce4..69dd0d0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,9 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.0-bin.zip networkTimeout=10000 +retries=0 +retryBackOffMs=500 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 index adff685..249efbb --- a/gradlew +++ b/gradlew @@ -20,7 +20,7 @@ ############################################################################## # -# Gradle start up script for POSIX generated by Gradle. +# gradlew start up script for POSIX generated by Gradle. # # Important for running: # @@ -29,7 +29,7 @@ # bash, then to run this script, type that shell name before the whole # command line, like: # -# ksh Gradle +# ksh gradlew # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/gradlew.bat b/gradlew.bat index e509b2d..a51ec4f 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,93 +1,82 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/settings.gradle b/settings.gradle index bbd421d..44ea176 100644 --- a/settings.gradle +++ b/settings.gradle @@ -13,11 +13,6 @@ dependencyResolutionManagement { repositories { google() mavenCentral() - mavenLocal { - content { - includeGroup("io.github.libxposed") - } - } } } rootProject.name = "DisableFlagSecure"