Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,15 @@ private void hookPackage(String packageName, ClassLoader classLoader) {
log(Log.ERROR, TAG, "hook ScreenCapture failed", t);
}
}
if (SYSTEMUI.equals(packageName) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) {
try {
hookOneUiSystemUiScreenshot(classLoader);
} catch (Throwable t) {
if (!(t instanceof ClassNotFoundException)) {
log(Log.ERROR, TAG, "hook OneUI SystemUI screenshot failed", t);
}
}
}
break;
default:
try {
Expand Down Expand Up @@ -444,9 +453,47 @@ private void hookOplus(ClassLoader classLoader) throws ClassNotFoundException {
hookMethods(longshotMainClazz, chain -> false, "hasSecure");
}

@RequiresApi(Build.VERSION_CODES.BAKLAVA)
private void hookOneUiSystemUiScreenshot(ClassLoader classLoader) throws ClassNotFoundException {
var screenshotDataClazz = classLoader.loadClass("com.android.systemui.screenshot.ScreenshotData");
var hookedConstructorCount = 0;
for (var constructor : screenshotDataClazz.getDeclaredConstructors()) {
if (!OneUiSystemUiScreenshotHook.isScreenshotDataConstructor(constructor)) {
continue;
}
hookedConstructorCount++;
hookE(constructor).intercept(chain -> {
var args = chain.getArgs().toArray();
OneUiSystemUiScreenshotHook.clearWorkProfileSecureLayer(args);
return chain.proceed(args);
});
}
if (hookedConstructorCount > 0) {
log(Log.INFO, TAG, "Hooked One UI Work Profile ScreenshotData constructors (count="
+ hookedConstructorCount + ")");
} else {
log(Log.WARN, TAG, "no supported ScreenshotData constructor was found");
}
}

private void hookOneUI(ClassLoader classLoader) throws ClassNotFoundException {
var wmScreenshotControllerClazz = classLoader.loadClass("com.android.server.wm.WmScreenshotController");
hookMethods(wmScreenshotControllerClazz, chain -> true, "canBeScreenshotTarget");

Arrays.stream(wmScreenshotControllerClazz.getDeclaredMethods())
.filter(OneUiWmScreenshotHook::isScreenshotAllowedByPolicyMethod)
.forEach(method -> hookE(method).intercept(chain -> {
var allowed = (boolean) chain.proceed();
return OneUiWmScreenshotHook.narrowScreenshotPolicyResult(chain.getThisObject(), allowed);
}));

Arrays.stream(wmScreenshotControllerClazz.getDeclaredMethods())
.filter(OneUiWmScreenshotHook::isPrimaryProfileScreenshotMethod)
.forEach(method -> hookE(method).intercept(chain -> {
var args = chain.getArgs().toArray();
OneUiWmScreenshotHook.forceIgnoreSecureContentPolicy(args);
return chain.proceed(args);
}));
}

private void hookMethods(Class<?> clazz, Hooker hooker, String... names) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package io.github.lsposed.disableflagsecure;

import java.lang.reflect.Constructor;

final class OneUiSystemUiScreenshotHook {
static final int TYPE_ARG_INDEX = 0;
static final int SECURE_LAYER_ARG_INDEX = 10;
static final int WORK_PROFILE_SCREENSHOT_TYPE = 3;

private OneUiSystemUiScreenshotHook() {
}

static boolean isScreenshotDataConstructor(Constructor<?> constructor) {
if (constructor == null) {
return false;
}
var params = constructor.getParameterTypes();
return params.length == 11
&& params[0] == int.class
&& params[1] == int.class
&& "android.os.UserHandle".equals(params[2].getName())
&& "android.content.ComponentName".equals(params[3].getName())
&& params[4] == int.class
&& "android.graphics.Rect".equals(params[5].getName())
&& "android.graphics.Insets".equals(params[6].getName())
&& "android.graphics.Bitmap".equals(params[7].getName())
&& params[8] == int.class
&& params[9] == boolean.class
&& params[10] == boolean.class;
}

static boolean clearWorkProfileSecureLayer(Object[] args) {
if (args == null || args.length <= SECURE_LAYER_ARG_INDEX) {
return false;
}
var typeArg = args[TYPE_ARG_INDEX];
if (!(typeArg instanceof Integer type) || type != WORK_PROFILE_SCREENSHOT_TYPE) {
return false;
}
var current = args[SECURE_LAYER_ARG_INDEX];
if (!(current instanceof Boolean secureLayer) || !secureLayer) {
return false;
}
args[SECURE_LAYER_ARG_INDEX] = false;
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package io.github.lsposed.disableflagsecure;

import android.graphics.Bitmap;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.SurfaceControl;

import java.lang.reflect.Method;

final class OneUiWmScreenshotHook {
static final int SECURE_CONTENT_POLICY_ARG_INDEX = 5;

private static final String POLICY_METHOD_NAME = "isScreenshotAllowedByPolicy";
private static final String SCREENSHOT_METHOD_NAME = "screenshot";
private static final String REASON_FOR_FAILURE_FIELD = "mReasonForFailure";
private static final int REASON_FLAG_SECURE = 16;
private static final int REASON_MDM = 32;

private OneUiWmScreenshotHook() {
}

static boolean isScreenshotAllowedByPolicyMethod(Method method) {
if (method == null || !POLICY_METHOD_NAME.equals(method.getName())) {
return false;
}
if (method.getReturnType() != boolean.class) {
return false;
}
var params = method.getParameterTypes();
return params.length == 1 && "DisplayContent".equals(params[0].getSimpleName());
}

static boolean isPrimaryProfileScreenshotMethod(Method method) {
if (method == null || !SCREENSHOT_METHOD_NAME.equals(method.getName())) {
return false;
}
if (method.getReturnType() != Bitmap.class) {
return false;
}
var params = method.getParameterTypes();
return params.length == 7
&& params[0] == IBinder.class
&& params[1] == Rect.class
&& params[2] == int.class
&& params[3] == int.class
&& params[4] == SurfaceControl.class
&& params[5] == boolean.class
&& params[6] == SurfaceControl[].class;
}

static boolean narrowScreenshotPolicyResult(Object controller, boolean originalAllowed) {
if (originalAllowed) {
return true;
}
var reason = readReasonForFailure(controller);
if (reason == null) {
return false;
}
return (reason & REASON_FLAG_SECURE) != 0 && (reason & REASON_MDM) == 0;
}

private static Integer readReasonForFailure(Object controller) {
if (controller == null) {
return null;
}
try {
var field = controller.getClass().getDeclaredField(REASON_FOR_FAILURE_FIELD);
if (field.getType() != int.class) {
return null;
}
field.setAccessible(true);
return field.getInt(controller);
} catch (ReflectiveOperationException | RuntimeException e) {
return null;
}
}

static boolean forceIgnoreSecureContentPolicy(Object[] args) {
if (args == null || args.length <= SECURE_CONTENT_POLICY_ARG_INDEX) {
return false;
}
var current = args[SECURE_CONTENT_POLICY_ARG_INDEX];
if (!(current instanceof Boolean ignorePolicy) || ignorePolicy) {
return false;
}
args[SECURE_CONTENT_POLICY_ARG_INDEX] = true;
return true;
}
}