Compare commits

..

No commits in common. "main" and "v4.1.0" have entirely different histories.
main ... v4.1.0

13 changed files with 103 additions and 142 deletions

View File

@ -29,13 +29,6 @@ body:
options:
- label: I am using latest debug CI version
required: true
- type: checkboxes
id: official
attributes:
label: LSPosed requirement
options:
- label: I am using official LSPosed version
required: true
- type: textarea
attributes:
label: Behavior

View File

@ -23,10 +23,10 @@ jobs:
path: libxposed/api
fetch-depth: 0
- name: set up JDK 21
- name: set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '21'
java-version: '17'
distribution: 'temurin'
cache: gradle
@ -50,6 +50,7 @@ jobs:
echo 'org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -XX:+UseParallelGC' >> ~/.gradle/gradle.properties
echo 'android.native.buildOutput=verbose' >> ~/.gradle/gradle.properties
# build dependencies
sed -i 's/VERSION_21/VERSION_17/g' libxposed/api/checks/build.gradle.kts
cd libxposed/api && ./gradlew publishToMavenLocal && cd ../..
# build DisableFlagSecure
chmod +x gradlew

View File

@ -1,14 +1,6 @@
# Enable Screenshot (formerly known as Disable FLAG_SECURE)
Enabling screenshots in apps that normally wouldn\'t allow it, and disabling screenshot(Android 14+) & screen record(Android 15+) detection.
**Unofficial LSPosed versions are not supported.**
## Supported OSes
- Android 12-16 Beta 3 (Custom ROMs are **not** supported)
- Xiaomi Hyper OS
- OPlus OS (Color OS/Realme UI/Oxygen OS)
- Samsung One UI
## Usage
1. Enable the module
2. Select **ONLY** recommended apps

1
SOURCE Normal file
View File

@ -0,0 +1 @@
https://github.com/Xposed-Modules-Repo/io.github.lsposed.disableflagsecure

View File

@ -1 +0,0 @@
https://github.com/LSPosed/DisableFlagSecure

View File

@ -3,15 +3,15 @@ plugins {
}
android {
compileSdk 36
buildToolsVersion "36.0.0"
compileSdk 35
buildToolsVersion "35.0.0"
defaultConfig {
applicationId "io.github.lsposed.disableflagsecure"
minSdkVersion 31
targetSdkVersion 36
minSdkVersion 27
targetSdkVersion 35
versionCode rootProject.ext.commitCount
versionName "4.2.0"
versionName "4.1.0"
}
Properties localProperties = new Properties()
@ -42,15 +42,14 @@ android {
} else {
signingConfig signingConfigs.debug
}
vcsInfo.include false
minifyEnabled true
proguardFiles 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
lint {
@ -62,7 +61,7 @@ android {
}
dependencies {
compileOnly 'androidx.annotation:annotation:1.9.1'
compileOnly 'androidx.annotation:annotation:1.8.2'
compileOnly 'io.github.libxposed:api:100'
compileOnly project(":libxposed-compat")
}

View File

@ -1,16 +1,18 @@
package io.github.lsposed.disableflagsecure;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.hardware.display.DisplayManager;
import android.os.Build;
import android.util.Log;
import android.view.SurfaceControl;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
@ -24,7 +26,6 @@ import io.github.libxposed.api.annotations.XposedHooker;
public class DisableFlagSecure extends XposedModule {
private static final String SYSTEMUI = "com.android.systemui";
private static final String OPLUS_APPPLATFORM = "com.oplus.appplatform";
private static final String OPLUS_SCREENSHOT = "com.oplus.screenshot";
private static final String FLYME_SYSTEMUIEX = "com.flyme.systemuiex";
private static final String MIUI_SCREENSHOT = "com.miui.screenshot";
@ -46,7 +47,7 @@ public class DisableFlagSecure extends XposedModule {
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
// Screen record detection (V~Baklava)
// Screen record detection (V)
try {
hookWindowManagerService(classLoader);
} catch (Throwable t) {
@ -55,15 +56,14 @@ public class DisableFlagSecure extends XposedModule {
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// Screenshot detection (U~Baklava)
// Screenshot detection (U~V)
try {
hookActivityTaskManagerService(classLoader);
} catch (Throwable t) {
log("hook ActivityTaskManagerService failed", t);
}
// Xiaomi HyperOS (U~Baklava)
// OS2.0.250220.1.WOCCNXM.PRE
// Xiaomi HyperOS (U)
try {
hookHyperOS(classLoader);
} catch (ClassNotFoundException ignored) {
@ -72,53 +72,57 @@ public class DisableFlagSecure extends XposedModule {
}
}
// ScreenCapture in WindowManagerService (S~Baklava)
try {
hookScreenCapture(classLoader);
} catch (Throwable t) {
log("hook ScreenCapture failed", t);
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// Blackout permission check (S~T)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// ScreenCapture in WindowManagerService (S~V)
try {
hookActivityManagerService(classLoader);
hookScreenCapture(classLoader);
} catch (Throwable t) {
log("hook ActivityManagerService failed", t);
log("hook ScreenCapture failed", t);
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// Blackout permission check (S~T)
try {
hookActivityManagerService(classLoader);
} catch (Throwable t) {
log("hook ActivityManagerService failed", t);
}
}
// WifiDisplay (S~V) / OverlayDisplay (S~V) / VirtualDisplay (U~V)
try {
hookDisplayControl(classLoader);
} catch (Throwable t) {
log("hook DisplayControl failed", t);
}
// VirtualDisplay with MediaProjection (S~V)
try {
hookVirtualDisplayAdapter(classLoader);
} catch (Throwable t) {
log("hook VirtualDisplayAdapter failed", t);
}
}
// WifiDisplay (S~Baklava) / OverlayDisplay (S~Baklava) / VirtualDisplay (U~Baklava)
try {
hookDisplayControl(classLoader);
} catch (Throwable t) {
log("hook DisplayControl failed", t);
}
// VirtualDisplay with MediaProjection (S~Baklava)
try {
hookVirtualDisplayAdapter(classLoader);
} catch (Throwable t) {
log("hook VirtualDisplayAdapter failed", t);
}
// OneUI
try {
hookScreenshotHardwareBuffer(classLoader);
} catch (Throwable t) {
if (!(t instanceof ClassNotFoundException)) {
log("hook ScreenshotHardwareBuffer failed", t);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// OneUI
try {
hookScreenshotHardwareBuffer(classLoader);
} catch (Throwable t) {
if (!(t instanceof ClassNotFoundException)) {
log("hook ScreenshotHardwareBuffer failed", t);
}
}
}
try {
hookOneUI(classLoader);
} catch (Throwable t) {
if (!(t instanceof ClassNotFoundException)) {
log("hook OneUI failed", t);
try {
hookOneUI(classLoader);
} catch (Throwable t) {
if (!(t instanceof ClassNotFoundException)) {
log("hook OneUI failed", t);
}
}
}
// secureLocked flag
// secureLocked flag (S-)
try {
// Screenshot
hookWindowState(classLoader);
@ -130,10 +134,8 @@ public class DisableFlagSecure extends XposedModule {
// dumpsys window screenshot systemQuickTileScreenshotOut display_id=0
try {
hookOplus(classLoader);
} catch (Throwable t) {
if (!(t instanceof ClassNotFoundException)) {
log("hook Oplus failed", t);
}
} catch (Throwable ignored) {
}
}
@ -145,17 +147,6 @@ public class DisableFlagSecure extends XposedModule {
var classLoader = param.getClassLoader();
var pn = param.getPackageName();
switch (pn) {
case OPLUS_SCREENSHOT:
// Oplus Screenshot 15.0.0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
try {
hookOplusScreenCapture(classLoader);
} catch (Throwable t) {
if (!(t instanceof ClassNotFoundException)) {
log("hook OplusScreenCapture failed", t);
}
}
}
case FLYME_SYSTEMUIEX:
case OPLUS_APPPLATFORM:
// Flyme SystemUI Ext 10.3.0
@ -169,10 +160,10 @@ public class DisableFlagSecure extends XposedModule {
}
case SYSTEMUI:
case MIUI_SCREENSHOT:
if (OPLUS_APPPLATFORM.equals(pn) || OPLUS_SCREENSHOT.equals(pn) ||
Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
// ScreenCapture in App (S~T) (OPlus S~V)
// TODO: test Oplus Baklava
if (OPLUS_APPPLATFORM.equals(pn) ||
(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) {
// ScreenCapture in App (S~T) (OPlus S-U)
try {
hookScreenCapture(classLoader);
} catch (Throwable t) {
@ -224,12 +215,19 @@ public class DisableFlagSecure extends XposedModule {
private void hookWindowState(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException {
var windowStateClazz = classLoader.loadClass("com.android.server.wm.WindowState");
var isSecureLockedMethod = windowStateClazz.getDeclaredMethod("isSecureLocked");
Method isSecureLockedMethod;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
isSecureLockedMethod = windowStateClazz.getDeclaredMethod("isSecureLocked");
} else {
var windowManagerServiceClazz = classLoader.loadClass("com.android.server.wm.WindowManagerService");
isSecureLockedMethod = windowManagerServiceClazz.getDeclaredMethod("isSecureLocked", windowStateClazz);
}
hook(isSecureLockedMethod, SecureLockedHooker.class);
}
private static Field captureSecureLayersField;
@TargetApi(Build.VERSION_CODES.S)
private void hookScreenCapture(ClassLoader classLoader) throws ClassNotFoundException, NoSuchFieldException {
var screenCaptureClazz = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE ?
classLoader.loadClass("android.window.ScreenCapture") :
@ -243,6 +241,7 @@ public class DisableFlagSecure extends XposedModule {
hookMethods(screenCaptureClazz, ScreenCaptureHooker.class, "nativeCaptureLayers");
}
@TargetApi(Build.VERSION_CODES.S)
private void hookDisplayControl(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException {
var displayControlClazz = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE ?
classLoader.loadClass("com.android.server.display.DisplayControl") :
@ -254,12 +253,13 @@ public class DisableFlagSecure extends XposedModule {
hook(method, CreateDisplayHooker.class);
}
@TargetApi(Build.VERSION_CODES.S)
private void hookVirtualDisplayAdapter(ClassLoader classLoader) throws ClassNotFoundException {
var displayControlClazz = classLoader.loadClass("com.android.server.display.VirtualDisplayAdapter");
hookMethods(displayControlClazz, CreateVirtualDisplayLockedHooker.class, "createVirtualDisplayLocked");
}
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
private void hookActivityTaskManagerService(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException {
var activityTaskManagerServiceClazz = classLoader.loadClass("com.android.server.wm.ActivityTaskManagerService");
var iBinderClazz = classLoader.loadClass("android.os.IBinder");
@ -268,7 +268,7 @@ public class DisableFlagSecure extends XposedModule {
hook(method, ReturnNullHooker.class);
}
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
@TargetApi(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");
@ -276,18 +276,20 @@ public class DisableFlagSecure extends XposedModule {
hook(method, ReturnFalseHooker.class);
}
@TargetApi(Build.VERSION_CODES.S)
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, CheckPermissionHooker.class);
}
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
private void hookHyperOS(ClassLoader classLoader) throws ClassNotFoundException {
var windowManagerServiceImplClazz = classLoader.loadClass("com.android.server.wm.WindowManagerServiceImpl");
hookMethods(windowManagerServiceImplClazz, ReturnFalseHooker.class, "notAllowCaptureDisplay");
}
@TargetApi(Build.VERSION_CODES.S)
private void hookScreenshotHardwareBuffer(ClassLoader classLoader) throws ClassNotFoundException, NoSuchMethodException {
var screenshotHardwareBufferClazz = classLoader.loadClass(
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE ?
@ -297,19 +299,13 @@ public class DisableFlagSecure extends XposedModule {
hook(method, ReturnFalseHooker.class);
}
@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, OplusScreenCaptureHooker.class);
}
private void hookOplus(ClassLoader classLoader) throws ClassNotFoundException {
// caller: com.android.server.wm.OplusLongshotWindowDump#dumpWindows
var longshotMainClazz = classLoader.loadClass("com.android.server.wm.OplusLongshotMainWindow");
hookMethods(longshotMainClazz, ReturnFalseHooker.class, "hasSecure");
}
@TargetApi(Build.VERSION_CODES.S)
private void hookOneUI(ClassLoader classLoader) throws ClassNotFoundException {
var wmScreenshotControllerClazz = classLoader.loadClass("com.android.server.wm.WmScreenshotController");
hookMethods(wmScreenshotControllerClazz, ReturnTrueHooker.class, "canBeScreenshotTarget");
@ -333,12 +329,9 @@ public class DisableFlagSecure extends XposedModule {
@BeforeInvocation
public static void before(@NonNull BeforeHookCallback callback) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
var stackTrace = new Throwable().getStackTrace();
for (int i = 4; i < stackTrace.length && i < 8; i++) {
var name = stackTrace[i].getMethodName();
if (name.equals("createVirtualDisplayLocked")) {
return;
}
String stack = Log.getStackTraceString(new Throwable());
if (stack.contains("createVirtualDisplayLocked")) {
return;
}
}
callback.getArgs()[1] = true;
@ -357,15 +350,6 @@ public class DisableFlagSecure extends XposedModule {
}
}
@XposedHooker
private static class OplusScreenCaptureHooker implements Hooker {
@BeforeInvocation
public static void before(@NonNull BeforeHookCallback callback) {
callback.getArgs()[0] = -1;
}
}
@XposedHooker
private static class ScreenCaptureHooker implements Hooker {
@ -386,7 +370,7 @@ public class DisableFlagSecure extends XposedModule {
@BeforeInvocation
public static void before(@NonNull BeforeHookCallback callback) {
var caller = (int) callback.getArgs()[2];
if (caller >= 10000 && callback.getArgs()[1] == null) {
if (caller != 1000 && callback.getArgs()[1] == null) {
// not os and not media projection
return;
}
@ -408,22 +392,12 @@ public class DisableFlagSecure extends XposedModule {
@BeforeInvocation
public static void before(@NonNull BeforeHookCallback callback) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
var walker = StackWalker.getInstance();
var match = walker.walk(frames -> frames
.map(StackWalker.StackFrame::getMethodName)
.limit(6)
.skip(2)
.anyMatch(s -> s.equals("setInitialSurfaceControlProperties") || s.equals("createSurfaceLocked")));
if (match) return;
} else {
var stackTrace = new Throwable().getStackTrace();
for (int i = 4; i < stackTrace.length && i < 8; i++) {
var name = stackTrace[i].getMethodName();
if (name.equals("setInitialSurfaceControlProperties") ||
name.equals("createSurfaceLocked")) {
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
String stack = Log.getStackTraceString(new Throwable());
// don't change surface flags, but passing other checks
if (stack.contains("setInitialSurfaceControlProperties")
|| stack.contains("createSurfaceLocked")) {
return;
}
}
callback.returnAndSkip(false);

View File

@ -3,4 +3,3 @@ com.android.systemui
com.flyme.systemuiex
com.miui.screenshot
com.oplus.appplatform
com.oplus.screenshot

View File

@ -1,10 +1,10 @@
plugins {
id 'com.android.application' version '8.9.1' apply false
id 'com.android.application' version '8.5.2' apply false
id 'org.lsposed.lsplugin.jgit' version "1.1"
}
tasks.register('clean', Delete) {
delete rootProject.layout.buildDirectory
delete rootProject.buildDir
}
var repo = jgit.repo(true)

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

5
gradlew vendored
View File

@ -86,7 +86,8 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@ -205,7 +206,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.

View File

@ -9,10 +9,12 @@ android {
defaultConfig {
minSdk 24
targetSdk 35
versionCode 1
versionName "1.0"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}