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
101 changes: 101 additions & 0 deletions Ports/iOSPort/nativeSources/IOSNative.m
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,76 @@
#endif
#endif

// iOS doesn't allow blocking a static screenshot, but it does expose a flag
// that tells us when the screen is being captured (recorded or mirrored). We
// use that signal to temporarily cover the app view with a black overlay.
static BOOL cn1_disableScreenshots = NO;
static UIView *cn1ScreenCaptureView = nil;
static id cn1ScreenCaptureObserver = nil;

static UIView *cn1_screenCaptureContainer() {
// Prefer the GL view controller's view. Fall back to the key window so
// we can still cover the app if the controller isn't ready yet.
UIView *container = [[CodenameOne_GLViewController instance] view];
if (container == nil) {
container = [UIApplication sharedApplication].keyWindow;
if (container == nil && [[UIApplication sharedApplication].windows count] > 0) {
container = [[UIApplication sharedApplication].windows objectAtIndex:0];
}
}
return container;
}

static void cn1_updateScreenCaptureBlocker() {
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
// Compile-time guard: screen-capture detection APIs were introduced in iOS 11.
// This keeps older SDKs building cleanly without referencing unavailable symbols.
if (!cn1_disableScreenshots) {
if (cn1ScreenCaptureView != nil) {
[cn1ScreenCaptureView removeFromSuperview];
cn1ScreenCaptureView = nil;
}
return;
}
if (![[UIScreen mainScreen] respondsToSelector:@selector(isCaptured)]) {
// Runtime guard: if running on an older iOS version, just skip.
return;
}
BOOL captured = NO;
if (@available(iOS 11.0, *)) {
captured = [UIScreen mainScreen].isCaptured;
}
if (captured) {
// If screen capture is active, hide the UI behind an overlay view.
UIView *container = cn1_screenCaptureContainer();
if (container == nil) {
return;
}
if (cn1ScreenCaptureView == nil) {
cn1ScreenCaptureView = [[UIView alloc] initWithFrame:container.bounds];
cn1ScreenCaptureView.backgroundColor = [UIColor blackColor];
cn1ScreenCaptureView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
cn1ScreenCaptureView.userInteractionEnabled = NO;
}
if (cn1ScreenCaptureView.superview != container) {
[cn1ScreenCaptureView removeFromSuperview];
[container addSubview:cn1ScreenCaptureView];
} else {
cn1ScreenCaptureView.frame = container.bounds;
}
} else if (cn1ScreenCaptureView != nil) {
[cn1ScreenCaptureView removeFromSuperview];
cn1ScreenCaptureView = nil;
}
#else
// Older SDKs don't have the screen-capture APIs. Nothing to do.
if (cn1ScreenCaptureView != nil) {
[cn1ScreenCaptureView removeFromSuperview];
cn1ScreenCaptureView = nil;
}
#endif
}

/*static JAVA_OBJECT utf8_constant = JAVA_NULL;
JAVA_OBJECT fromNSString(NSString* str)
{
Expand Down Expand Up @@ -2025,6 +2095,37 @@ void com_codename1_impl_ios_IOSNative_unlockScreen__(CN1_THREAD_STATE_MULTI_ARG
});
}

void com_codename1_impl_ios_IOSNative_setDisableScreenshots___boolean(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject, JAVA_BOOLEAN disable)
{
BOOL shouldDisable = disable ? YES : NO;
dispatch_async(dispatch_get_main_queue(), ^{
cn1_disableScreenshots = shouldDisable;
if (cn1ScreenCaptureObserver != nil) {
[[NSNotificationCenter defaultCenter] removeObserver:cn1ScreenCaptureObserver];
cn1ScreenCaptureObserver = nil;
}
if (cn1ScreenCaptureView != nil) {
[cn1ScreenCaptureView removeFromSuperview];
cn1ScreenCaptureView = nil;
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000
if (cn1_disableScreenshots && [[UIScreen mainScreen] respondsToSelector:@selector(isCaptured)]) {
if (@available(iOS 11.0, *)) {
// Listen for capture state changes so we can add/remove the overlay.
cn1ScreenCaptureObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIScreenCapturedDidChangeNotification
object:[UIScreen mainScreen]
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *notification) {
cn1_updateScreenCaptureBlocker();
}];
}
}
#endif
// Ensure the overlay reflects the current capture state immediately.
cn1_updateScreenCaptureBlocker();
});
}

extern void vibrateDevice();
void com_codename1_impl_ios_IOSNative_vibrate___int(CN1_THREAD_STATE_MULTI_ARG JAVA_OBJECT instanceObject, JAVA_INT duration) {
vibrateDevice();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ public void init(Object m) {
life = (Lifecycle)m;
}
VideoCaptureConstraints.init(new IOSVideoCaptureConstraintsCompiler());
if("true".equals(Display.getInstance().getProperty("DisableScreenshots", ""))) {
nativeInstance.setDisableScreenshots(true);
}
}

public void setThreadPriority(Thread t, int p) {
Expand Down
1 change: 1 addition & 0 deletions Ports/iOSPort/src/com/codename1/impl/ios/IOSNative.java
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ byte[] loadResource(String name, String type) {
native void unlockOrientation();
native void lockScreen();
native void unlockScreen();
native void setDisableScreenshots(boolean disable);

native void vibrate(int duration);

Expand Down
4 changes: 3 additions & 1 deletion docs/developer-guide/security.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,9 @@ This is useful as it prevents the encrypted preferences from colliding with the

One of the common security features some apps expect is the ability to block a screenshot. In the past apps like snapchat required that you touch the screen to view a photo to block the ability to grab a screenshot (on iOS). This no longer works...

Blocking screenshots is an Android specific feature that can't be implemented on iOS. This is implemented by classifying the app window as secure and you can do that via the build hint `android.disableScreenshots=true`. Once that is added screenshots should no longer work for the app, this might impact other things as well such as the task view which will no longer show the screenshot either.
Blocking screenshots is implemented by classifying the app window as secure on Android. You can enable this via the build hint `android.disableScreenshots=true`. Once that is added screenshots should no longer work for the app, this might impact other things as well such as the task view which will no longer show the screenshot either.

On iOS, you cannot prevent a user from taking a static screenshot, but you can block screen capture/recording while it is active. This uses `UIScreen.isCaptured` and its change notification to hide the app contents while a capture session is active. Enable this behavior with the build hint `ios.disableScreenshots=true`.

=== Blocking Copy & Paste

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,10 @@ public void usesClassMethod(String cls, String method) {
if(request.getArg("ios.newStorageLocation", "true").equals("true")) {
newStorage = " Display.getInstance().setProperty(\"iosNewStorage\", \"true\");\n";
}
String disableScreenshots = "";
if (request.getArg("ios.disableScreenshots", "false").equalsIgnoreCase("true")) {
disableScreenshots = " Display.getInstance().setProperty(\"DisableScreenshots\", \"true\");\n";
}

String didEnterBackground = " stopped = true;\n"
+ " final long bgTask = com.codename1.impl.ios.IOSImplementation.beginBackgroundTask();\n"
Expand Down Expand Up @@ -967,6 +971,7 @@ public void usesClassMethod(String cls, String method) {
+ " Display.getInstance().setProperty(\"AppVersion\", APPLICATION_VERSION);\n"
+ " Display.getInstance().setProperty(\"AppName\", APPLICATION_NAME);\n"
+ newStorage
+ disableScreenshots
+ adPadding
+ integrateFacebook
+ integrateGoogleConnect
Expand Down
Loading