Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -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
88 changes: 40 additions & 48 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ name: Android CI

on:
push:
branches:
- main
pull_request:
workflow_dispatch:

Expand All @@ -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

Comment thread
Howard20181 marked this conversation as resolved.
- name: Upload debug artifact
uses: actions/upload-artifact@v7
with:
path: app/build/outputs/apk/debug/*.apk
archive: false
Comment thread
Howard20181 marked this conversation as resolved.
8 changes: 4 additions & 4 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ plugins {

android {
compileSdk {
version = release(36) {
version = release(37) {
minorApiLevel = 1
}
}
Expand All @@ -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"
}
Expand Down Expand Up @@ -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'
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -27,6 +31,8 @@ public class DisableFlagSecure extends XposedModule {
private static final String MIUI_SCREENSHOT = "com.miui.screenshot";

private static XposedModule module;
private Pair<String, ClassLoader> param;
private final Set<String> hookedIds = new HashSet<>();

@Override
public void onModuleLoaded(@NonNull ModuleLoadedParam param) {
Expand All @@ -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 {
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand All @@ -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();
}
});
}
Comment thread
Howard20181 marked this conversation as resolved.

private void deoptimizeSystemServer(ClassLoader classLoader) throws ClassNotFoundException {
deoptimizeMethods(
classLoader.loadClass("com.android.server.wm.WindowStateAnimator"),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -342,21 +387,21 @@ 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)
private void hookWindowManagerService(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException {
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();
Expand All @@ -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);
Expand All @@ -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")
Expand Down
5 changes: 3 additions & 2 deletions app/src/main/resources/META-INF/xposed/module.prop
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
minApiVersion=101
targetApiVersion=101
staticScope=true
targetApiVersion=102
staticScope=true
autoHotReload=true
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
@@ -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"
}

Expand Down
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
4 changes: 3 additions & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -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
Comment thread
Howard20181 marked this conversation as resolved.
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Loading