Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Separate Privacy Screen and Screenshot Protection #113

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
@@ -1,52 +1,189 @@
package com.getcapacitor.plugin.privacyscreen;

import android.graphics.Color;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.util.Log;
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;

public class PrivacyScreen {

private final PrivacyScreenPlugin plugin;
private final PrivacyScreenConfig config;
private View focusMonitorView;
private View privacyOverlay;
private static final String TAG = "PrivacyScreen";

private boolean isPrivacyOverlayEnabled = false;
private boolean isScreenshotProtectionEnabled = false;

public PrivacyScreen(PrivacyScreenPlugin plugin, PrivacyScreenConfig config) {
this.plugin = plugin;
this.config = config;

boolean isEnabled = config.isEnabled();
if (isEnabled) {
addFlags();
Log.d(TAG, "Initializing PrivacyScreen with current configuration settings.");

if (config.isScreenshotProtectionEnabled()) {
Log.d(TAG, "Screenshot protection is enabled in the configuration.");
enableScreenshotProtection(() -> Log.d(TAG, "Screenshot protection has been enabled."));
}

plugin.getActivity().runOnUiThread(() -> {
FrameLayout root = plugin.getActivity().findViewById(android.R.id.content);
focusMonitorView = new View(plugin.getContext());
focusMonitorView.setLayoutParams(new FrameLayout.LayoutParams(1, 1));
focusMonitorView.setBackgroundColor(Color.TRANSPARENT);

focusMonitorView.getViewTreeObserver().addOnWindowFocusChangeListener(hasFocus -> {
if (!hasFocus) {
addPrivacyOverlay();
Log.d(TAG, "Window has lost focus, adding privacy overlay.");
} else {
removePrivacyOverlay();
Log.d(TAG, "Window has gained focus, removing privacy overlay.");
}
});

root.addView(focusMonitorView);
focusMonitorView.requestFocus();
});

// Register lifecycle callbacks
plugin.getActivity().getApplication().registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() {

@Override
public void onActivityStarted(Activity activity) {
if (activity == plugin.getActivity()) {
addScreenshotProtectionFlags();
Log.d(TAG, "Activity started, removing overlay if present.");
}
}

@Override
public void onActivityStopped(Activity activity) {
if (activity == plugin.getActivity()) {
addPrivacyOverlay();
Log.d(TAG, "Activity stopped, adding overlay.");
}
}
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {}
@Override public void onActivityResumed(Activity activity) {}
@Override public void onActivityPaused(Activity activity) {}
@Override public void onActivityDestroyed(Activity activity) {
if (activity == plugin.getActivity()) {
plugin.getActivity().getApplication().unregisterActivityLifecycleCallbacks(this);
}
}
});
}

public void enable(EnableCallback callback) {
plugin
.getBridge()
.executeOnMainThread(
() -> {
addFlags();
callback.success();

public void enablePrivacyScreen(Runnable callback) {
if (!isPrivacyOverlayEnabled) {
plugin.getBridge().executeOnMainThread(() -> {
addPrivacyOverlay();
Log.d(TAG, "Privacy overlay added to the window.");
if (callback != null) {
callback.run();
}
);
});
}
}

public void disable(DisableCallback callback) {
plugin
.getBridge()
.executeOnMainThread(
() -> {
clearFlags();
callback.success();
public void disablePrivacyScreen(Runnable callback) {
if (isPrivacyOverlayEnabled) {
plugin.getBridge().executeOnMainThread(() -> {
removePrivacyOverlay();
Log.d(TAG, "Privacy overlay removed from the window.");
if (callback != null) {
callback.run();
}
});
}
}

public void enableScreenshotProtection(Runnable callback) {
if (!isScreenshotProtectionEnabled) {
plugin.getBridge().executeOnMainThread(() -> {
addScreenshotProtectionFlags();
Log.d(TAG, "Screenshot protection flags have been added.");
if (callback != null) {
callback.run();
}
);
});
}
}

private void addFlags() {
public void disableScreenshotProtection(Runnable callback) {
if (isScreenshotProtectionEnabled) {
plugin.getBridge().executeOnMainThread(() -> {
clearScreenshotProtectionFlags();
Log.d(TAG, "Screenshot protection flags have been cleared.");
if (callback != null) {
callback.run();
}
});
}
}

private void addPrivacyOverlay() {
Window window = plugin.getActivity().getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE);
FrameLayout root = (FrameLayout) window.getDecorView().findViewById(android.R.id.content);
if (privacyOverlay == null) {
privacyOverlay = new View(plugin.getContext());
privacyOverlay.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
privacyOverlay.setBackgroundColor(0xFFFFFFFF);
Log.d(TAG, "Creating new privacy overlay.");
} else {
// If privacyOverlay already has a parent, remove it from that parent first
if (privacyOverlay.getParent() != null) {
((ViewGroup) privacyOverlay.getParent()).removeView(privacyOverlay);
}
}
root.addView(privacyOverlay);
isPrivacyOverlayEnabled = true;
Log.d(TAG, "Privacy overlay added to the activity's root view.");
}

private void removePrivacyOverlay() {
if (privacyOverlay != null) {
if (privacyOverlay.getParent() != null) {
// Cast the parent to ViewGroup before removing the view
ViewGroup parent = (ViewGroup) privacyOverlay.getParent();
parent.removeView(privacyOverlay);
Log.d(TAG, "Privacy overlay removed from the activity's root view.");
} else {
Log.d(TAG, "Privacy overlay was not attached to any parent.");
}
privacyOverlay = null; // Nullify the reference regardless of whether it was removed or not
isPrivacyOverlayEnabled = false;
} else {
Log.d(TAG, "No privacy overlay to remove.");
}
}

private void addScreenshotProtectionFlags() {

plugin
.getActivity()
.getWindow()
.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);

isScreenshotProtectionEnabled = true;
Log.d(TAG, "FLAG_SECURE added to window.");
}

private void clearFlags() {
private void clearScreenshotProtectionFlags() {
Window window = plugin.getActivity().getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
isScreenshotProtectionEnabled = false;
Log.d(TAG, "FLAG_SECURE cleared from window.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@

public class PrivacyScreenConfig {

private boolean enable = true;
private boolean privacyScreenEnabled = true;
private boolean screenshotProtectionEnabled = true;

public boolean isEnabled() {
return enable;
public boolean isPrivacyScreenEnabled() {
return privacyScreenEnabled;
}

public void setEnable(boolean enable) {
this.enable = enable;
public void setPrivacyScreenEnabled(boolean privacyScreenEnabled) {
this.privacyScreenEnabled = privacyScreenEnabled;
}

public boolean isScreenshotProtectionEnabled() {
return screenshotProtectionEnabled;
}

public void setScreenshotProtectionEnabled(boolean screenshotProtectionEnabled) {
this.screenshotProtectionEnabled = screenshotProtectionEnabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,43 @@
@CapacitorPlugin(name = "PrivacyScreen")
public class PrivacyScreenPlugin extends Plugin {

private PrivacyScreen implementation;
private PrivacyScreen privacyScreen;

public void load() {
PrivacyScreenConfig config = getPrivacyScreenConfig();
implementation = new PrivacyScreen(this, config);
privacyScreen = new PrivacyScreen(this, config);
}

@PluginMethod
public void enable(final PluginCall call) {
implementation.enable(() -> call.resolve());
public void enablePrivacyScreen(final PluginCall call) {
privacyScreen.enablePrivacyScreen(() -> call.resolve());
}

@PluginMethod
public void disable(final PluginCall call) {
implementation.disable(() -> call.resolve());
public void disablePrivacyScreen(final PluginCall call) {
privacyScreen.disablePrivacyScreen(() -> call.resolve());
}

@PluginMethod
public void enableScreenshotProtection(final PluginCall call) {
privacyScreen.enableScreenshotProtection(() -> call.resolve());
}

@PluginMethod
public void disableScreenshotProtection(final PluginCall call) {
privacyScreen.disableScreenshotProtection(() -> call.resolve());
}

private PrivacyScreenConfig getPrivacyScreenConfig() {
PrivacyScreenConfig config = new PrivacyScreenConfig();

Boolean enable = getConfig().getBoolean("enable", config.isEnabled());
config.setEnable(enable);
// Fetch and set privacy screen enabled state
Boolean privacyScreenEnabled = getConfig().getBoolean("privacyScreenEnabled", config.isPrivacyScreenEnabled());
config.setPrivacyScreenEnabled(privacyScreenEnabled);

// Fetch and set screenshot protection enabled state
Boolean screenshotProtectionEnabled = getConfig().getBoolean("screenshotProtectionEnabled", config.isScreenshotProtectionEnabled());
config.setScreenshotProtectionEnabled(screenshotProtectionEnabled);

return config;
}
Expand Down