diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index 1ac179fd..7ddc8878 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -24,6 +24,7 @@ #if defined(Q_OS_IOS) #include "platforms/ios/ios_controller.h" + #include #endif #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) @@ -98,6 +99,10 @@ void AmneziaApplication::init() connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs); + AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled()); + connect(m_settings.get(), &Settings::screenshotsEnabledChanged, + AndroidController::instance(), &AndroidController::setScreenshotsEnabled); + connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer); @@ -134,6 +139,11 @@ void AmneziaApplication::init() m_pageController->goToPageSettingsBackup(); m_settingsController->importBackupFromOutside(filePath); }); + + AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); + connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { + AmneziaVPN::toggleScreenshots(enabled); + }); #endif m_notificationHandler.reset(NotificationHandler::create(nullptr)); diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt index f01e8df6..ff25ab05 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -14,6 +14,7 @@ import android.os.IBinder import android.os.Looper import android.os.Message import android.os.Messenger +import android.view.WindowManager.LayoutParams import android.webkit.MimeTypeMap import android.widget.Toast import androidx.annotation.MainThread @@ -453,4 +454,13 @@ class AmneziaActivity : QtActivity() { Log.v(TAG, "Clear logs") Log.clearLogs() } + + @Suppress("unused") + fun setScreenshotsEnabled(enabled: Boolean) { + Log.v(TAG, "Set screenshots enabled: $enabled") + mainScope.launch { + val flag = if (enabled) 0 else LayoutParams.FLAG_SECURE + window.setFlags(flag, LayoutParams.FLAG_SECURE) + } + } } diff --git a/client/cmake/ios.cmake b/client/cmake/ios.cmake index 824c1caf..ce6c8f94 100644 --- a/client/cmake/ios.cmake +++ b/client/cmake/ios.cmake @@ -107,6 +107,7 @@ target_sources(${PROJECT} PRIVATE ${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift ${CLIENT_ROOT_DIR}/platforms/ios/Log.swift ${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift + ${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift ) target_sources(${PROJECT} PRIVATE diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp index b789f0e0..e18fd864 100644 --- a/client/platforms/android/android_controller.cpp +++ b/client/platforms/android/android_controller.cpp @@ -204,6 +204,11 @@ void AndroidController::clearLogs() callActivityMethod("clearLogs", "()V"); } +void AndroidController::setScreenshotsEnabled(bool enabled) +{ + callActivityMethod("setScreenshotsEnabled", "(Z)V", enabled); +} + // Moving log processing to the Android side jclass AndroidController::log; jmethodID AndroidController::logDebug; diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h index 3491d837..6c104f3c 100644 --- a/client/platforms/android/android_controller.h +++ b/client/platforms/android/android_controller.h @@ -39,6 +39,7 @@ public: void setSaveLogs(bool enabled); void exportLogsFile(const QString &fileName); void clearLogs(); + void setScreenshotsEnabled(bool enabled); static bool initLogging(); static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message); diff --git a/client/platforms/ios/QtAppDelegate.mm b/client/platforms/ios/QtAppDelegate.mm index 70e54400..bd7ad6b1 100644 --- a/client/platforms/ios/QtAppDelegate.mm +++ b/client/platforms/ios/QtAppDelegate.mm @@ -3,7 +3,6 @@ #include -UIView *_screen; @implementation QIOSApplicationDelegate (AmneziaVPNDelegate) @@ -15,19 +14,6 @@ UIView *_screen; return YES; } -- (void)applicationWillResignActive:(UIApplication *)application -{ - // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. - // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. - _screen = [UIScreen.mainScreen snapshotViewAfterScreenUpdates: false]; - UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle: UIBlurEffectStyleDark]; - UIVisualEffectView *blurBackground = [[UIVisualEffectView alloc] initWithEffect: blurEffect]; - [_screen addSubview: blurBackground]; - blurBackground.frame = _screen.frame; - UIWindow *_window = UIApplication.sharedApplication.keyWindow; - [_window addSubview: _screen]; -} - - (void)applicationDidEnterBackground:(UIApplication *)application { // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. @@ -41,17 +27,6 @@ UIView *_screen; NSLog(@"In the foreground"); } -- (void)applicationDidBecomeActive:(UIApplication *)application -{ - // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. - [_screen removeFromSuperview]; -} - -- (void)applicationWillTerminate:(UIApplication *)application -{ - // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. -} - -(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { // We will add content here soon. NSLog(@"In the completionHandler"); diff --git a/client/platforms/ios/ScreenProtection.swift b/client/platforms/ios/ScreenProtection.swift new file mode 100644 index 00000000..1355dc13 --- /dev/null +++ b/client/platforms/ios/ScreenProtection.swift @@ -0,0 +1,87 @@ +import UIKit + +public func toggleScreenshots(_ isEnabled: Bool) { + let window = UIApplication.shared.keyWindows.first! + + if isEnabled { + ScreenProtection.shared.disable(for: window.rootViewController!.view) + } else { + ScreenProtection.shared.enable(for: window.rootViewController!.view) + } +} + +extension UIApplication { + var keyWindows: [UIWindow] { + connectedScenes + .compactMap { + if #available(iOS 15.0, *) { + ($0 as? UIWindowScene)?.keyWindow + } else { + ($0 as? UIWindowScene)?.windows.first { $0.isKeyWindow } + } + } + } +} + +class ScreenProtection { + public static let shared = ScreenProtection() + + var pairs = [ProtectionPair]() + + private var blurView: UIVisualEffectView? + private var recordingObservation: NSKeyValueObservation? + + public func enable(for view: UIView) { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + view.subviews.forEach { + self.pairs.append(ProtectionPair(from: $0)) + } + } + } + + public func disable(for view: UIView) { + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self.pairs.forEach { + $0.removeProtection() + } + + self.pairs.removeAll() + } + } +} + +struct ProtectionPair { + let textField: UITextField + let layer: CALayer + + init(from view: UIView) { + let secureTextField = UITextField() + secureTextField.backgroundColor = .clear + secureTextField.translatesAutoresizingMaskIntoConstraints = false + secureTextField.isSecureTextEntry = true + + view.insertSubview(secureTextField, at: 0) + secureTextField.isUserInteractionEnabled = false + + view.layer.superlayer?.addSublayer(secureTextField.layer) + secureTextField.layer.sublayers?.last?.addSublayer(view.layer) + + secureTextField.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true + secureTextField.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true + secureTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true + secureTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true + + self.init(textField: secureTextField, layer: view.layer) + } + + init(textField: UITextField, layer: CALayer) { + self.textField = textField + self.layer = layer + } + + func removeProtection() { + textField.superview?.superview?.layer.addSublayer(layer) + textField.layer.removeFromSuperlayer() + textField.removeFromSuperview() + } +} diff --git a/client/settings.h b/client/settings.h index 613d567b..b11747e1 100644 --- a/client/settings.h +++ b/client/settings.h @@ -185,12 +185,14 @@ public: void setScreenshotsEnabled(bool enabled) { setValue("Conf/screenshotsEnabled", enabled); + emit screenshotsEnabledChanged(enabled); } void clearSettings(); signals: void saveLogsChanged(bool enabled); + void screenshotsEnabledChanged(bool enabled); void serverRemoved(int serverIndex); void settingsCleared(); diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 4aa64533..658bbb19 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -7,9 +7,7 @@ #include "ui/qautostart.h" #include "version.h" #ifdef Q_OS_ANDROID - #include "platforms/android/android_utils.h" #include "platforms/android/android_controller.h" - #include #endif #ifdef Q_OS_IOS @@ -29,20 +27,6 @@ SettingsController::SettingsController(const QSharedPointer &serve m_settings(settings) { m_appVersion = QString("%1 (%2, %3)").arg(QString(APP_VERSION), __DATE__, GIT_COMMIT_HASH); - -#ifdef Q_OS_ANDROID - if (!m_settings->isScreenshotsEnabled()) { - // Set security screen for Android app - AndroidUtils::runOnAndroidThreadSync([]() { - QJniObject activity = AndroidUtils::getActivity(); - QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); - if (window.isValid()) { - const int FLAG_SECURE = 8192; - window.callMethod("addFlags", "(I)V", FLAG_SECURE); - } - }); - } -#endif } void SettingsController::toggleAmneziaDns(bool enable) @@ -204,19 +188,6 @@ bool SettingsController::isScreenshotsEnabled() void SettingsController::toggleScreenshotsEnabled(bool enable) { m_settings->setScreenshotsEnabled(enable); -#ifdef Q_OS_ANDROID - std::string command = enable ? "clearFlags" : "addFlags"; - - // Set security screen for Android app - AndroidUtils::runOnAndroidThreadSync([&command]() { - QJniObject activity = AndroidUtils::getActivity(); - QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); - if (window.isValid()) { - const int FLAG_SECURE = 8192; - window.callMethod(command.c_str(), "(I)V", FLAG_SECURE); - } - }); -#endif } bool SettingsController::isCameraPresent() diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 06ce907a..335d59a4 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -160,7 +160,6 @@ PageType { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter text: ServersModel.defaultServerDescriptionCollapsed } - } expandedContent: Item { id: serverMenuContainer