Skip to content

Commit

Permalink
Screenshot Detection
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbel committed Apr 2, 2024
1 parent 7656a21 commit ecc8b28
Show file tree
Hide file tree
Showing 24 changed files with 162 additions and 0 deletions.
16 changes: 16 additions & 0 deletions androidApp/src/main/kotlin/org/michaelbel/movies/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import androidx.fragment.app.FragmentActivity
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.michaelbel.movies.common.ktx.launchAndCollectIn
import org.michaelbel.movies.ui.ktx.resolveNotificationPreferencesIntent
import org.michaelbel.movies.ui.ktx.setScreenshotBlockEnabled
import org.michaelbel.movies.ui.ktx.supportRegisterScreenCaptureCallback
import org.michaelbel.movies.ui.ktx.supportUnregisterScreenCaptureCallback
import org.michaelbel.movies.ui.shortcuts.installShortcuts

internal class MainActivity: FragmentActivity() {
Expand All @@ -26,8 +29,21 @@ internal class MainActivity: FragmentActivity() {
}
resolveNotificationPreferencesIntent()
viewModel.run {
isScreenshotBlockEnabled.launchAndCollectIn(this@MainActivity) { enabled ->
window.setScreenshotBlockEnabled(enabled)
}
authenticateFlow.launchAndCollectIn(this@MainActivity) { authenticate(this@MainActivity) }
cancelFlow.launchAndCollectIn(this@MainActivity) { finish() }
}
}

override fun onStart() {
super.onStart()
supportRegisterScreenCaptureCallback()
}

override fun onStop() {
super.onStop()
supportUnregisterScreenCaptureCallback()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ internal class MainViewModel(
initialValue = ThemeData.Default
)

val isScreenshotBlockEnabled: StateFlow<Boolean> = interactor.isScreenshotBlockEnabled
.stateIn(
scope = this,
started = SharingStarted.Lazily,
initialValue = false
)

init {
fetchBiometric()
fetchRemoteConfig()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ interface SettingsInteractor {

val isBiometricEnabled: Flow<Boolean>

val isScreenshotBlockEnabled: Flow<Boolean>

suspend fun isBiometricEnabledAsync(): Boolean

suspend fun selectTheme(
Expand Down Expand Up @@ -47,4 +49,8 @@ interface SettingsInteractor {
suspend fun setBiometricEnabled(
enabled: Boolean
)

suspend fun setScreenshotBlockEnabled(
enabled: Boolean
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ internal class SettingsInteractorImpl(

override val isBiometricEnabled: Flow<Boolean> = settingsRepository.isBiometricEnabled

override val isScreenshotBlockEnabled: Flow<Boolean> = settingsRepository.isScreenshotBlockEnabled

override suspend fun isBiometricEnabledAsync(): Boolean {
return settingsRepository.isBiometricEnabledAsync()
}
Expand Down Expand Up @@ -80,4 +82,10 @@ internal class SettingsInteractorImpl(
settingsRepository.setBiometricEnabled(enabled)
}
}

override suspend fun setScreenshotBlockEnabled(enabled: Boolean) {
withContext(dispatchers.main) {
settingsRepository.setScreenshotBlockEnabled(enabled)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class MoviesPreferences(
val isBiometricEnabledFlow: Flow<Boolean?>
get() = dataStore.data.map { preferences -> preferences[PREFERENCE_BIOMETRIC_KEY] }

val isScreenshotBlockEnabledFlow: Flow<Boolean?>
get() = dataStore.data.map { preferences -> preferences[PREFERENCE_SCREENSHOT_BLOCK_KEY] }

val paletteKeyFlow: Flow<Int?>
get() = dataStore.data.map { preferences -> preferences[PREFERENCE_PALETTE_KEY] }

Expand Down Expand Up @@ -125,6 +128,12 @@ class MoviesPreferences(
}
}

suspend fun setScreenshotBlockEnabled(enabled: Boolean) {
dataStore.edit { preferences ->
preferences[PREFERENCE_SCREENSHOT_BLOCK_KEY] = enabled
}
}

suspend fun setPaletteKey(paletteKey: Int) {
dataStore.edit { preferences ->
preferences[PREFERENCE_PALETTE_KEY] = paletteKey
Expand All @@ -149,5 +158,6 @@ class MoviesPreferences(
private val PREFERENCE_BIOMETRIC_KEY = booleanPreferencesKey("biometric")
private val PREFERENCE_PALETTE_KEY = intPreferencesKey("palette")
private val PREFERENCE_SEED_COLOR_KEY = intPreferencesKey("seed_color")
private val PREFERENCE_SCREENSHOT_BLOCK_KEY = booleanPreferencesKey("screenshot_block")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ interface SettingsRepository {

val isBiometricEnabled: Flow<Boolean>

val isScreenshotBlockEnabled: Flow<Boolean>

suspend fun isBiometricEnabledAsync(): Boolean

suspend fun selectTheme(
Expand Down Expand Up @@ -47,4 +49,8 @@ interface SettingsRepository {
suspend fun setBiometricEnabled(
enabled: Boolean
)

suspend fun setScreenshotBlockEnabled(
enabled: Boolean
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ internal class SettingsRepositoryImpl(
enabled ?: false
}

override val isScreenshotBlockEnabled: Flow<Boolean> = preferences.isScreenshotBlockEnabledFlow.map { enabled ->
enabled ?: false
}

override suspend fun isBiometricEnabledAsync(): Boolean {
return preferences.isBiometricEnabledAsync()
}
Expand Down Expand Up @@ -79,4 +83,8 @@ internal class SettingsRepositoryImpl(
override suspend fun setBiometricEnabled(enabled: Boolean) {
preferences.setBiometricEnabled(enabled)
}

override suspend fun setScreenshotBlockEnabled(enabled: Boolean) {
preferences.setScreenshotBlockEnabled(enabled)
}
}
2 changes: 2 additions & 0 deletions core/ui-kmp/src/androidMain/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android">

<uses-permission android:name="android.permission.DETECT_SCREEN_CAPTURE" />

<application>

<service
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.michaelbel.movies.ui.ktx

import android.app.Activity
import android.os.Build

private val screenCaptureCallback: Any
get() {
return if (Build.VERSION.SDK_INT >= 34) {
Activity.ScreenCaptureCallback {}
} else {
Unit
}
}

fun Activity.supportRegisterScreenCaptureCallback() {
if (Build.VERSION.SDK_INT >= 34) {
registerScreenCaptureCallback(mainExecutor, screenCaptureCallback as Activity.ScreenCaptureCallback)
}
}

fun Activity.supportUnregisterScreenCaptureCallback() {
if (Build.VERSION.SDK_INT >= 34) {
unregisterScreenCaptureCallback(screenCaptureCallback as Activity.ScreenCaptureCallback)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.michaelbel.movies.ui.ktx

import android.view.Window
import android.view.WindowManager

fun Window.setScreenshotBlockEnabled(enabled: Boolean) {
if (enabled) {
setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
} else {
clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@
<string name="settings_update">Обнови приложение</string>
<string name="settings_update_description">Используя In-App Update API</string>
<string name="settings_palette_colors">Палитра цветов</string>
<string name="settings_screenshots">Скриншоты</string>
<string name="settings_screenshots_description">Запретить делать скриншоты</string>

<string name="appwidget_description">Отображает предстоящие фильмы</string>
<string name="appwidget_title">Скоро в кино</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@
<string name="settings_update">Update App</string>
<string name="settings_update_description">Using In-App Update API</string>
<string name="settings_palette_colors">Palette Colors</string>
<string name="settings_screenshots">Screenshots</string>
<string name="settings_screenshots_description">Block taking screenshots</string>

<string name="appwidget_description">Display upcoming movies</string>
<string name="appwidget_title">Upcoming Movies</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import androidx.compose.material.icons.outlined.LocalMovies
import androidx.compose.material.icons.outlined.LocationOn
import androidx.compose.material.icons.outlined.Notifications
import androidx.compose.material.icons.outlined.Palette
import androidx.compose.material.icons.outlined.Screenshot
import androidx.compose.material.icons.outlined.Search
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.Share
Expand Down Expand Up @@ -79,6 +80,7 @@ object MoviesIcons {
val LocalMovies = Icons.Outlined.LocalMovies
val Notifications = Icons.Outlined.Notifications
val Palette = Icons.Outlined.Palette
val Screenshot = Icons.Outlined.Screenshot
val Search = Icons.Outlined.Search
val Settings = Icons.Outlined.Settings
val Share = Icons.Outlined.Share
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ import movies.core.ui_kmp.generated.resources.settings_post_notifications_grante
import movies.core.ui_kmp.generated.resources.settings_post_notifications_should_request
import movies.core.ui_kmp.generated.resources.settings_review
import movies.core.ui_kmp.generated.resources.settings_review_description
import movies.core.ui_kmp.generated.resources.settings_screenshots
import movies.core.ui_kmp.generated.resources.settings_screenshots_description
import movies.core.ui_kmp.generated.resources.settings_theme
import movies.core.ui_kmp.generated.resources.settings_theme_amoled
import movies.core.ui_kmp.generated.resources.settings_theme_dark
Expand Down Expand Up @@ -227,6 +229,8 @@ object MoviesStrings {
val settings_update = Res.string.settings_update
val settings_update_description = Res.string.settings_update_description
val settings_palette_colors = Res.string.settings_palette_colors
val settings_screenshots = Res.string.settings_screenshots
val settings_screenshots_description = Res.string.settings_screenshots_description

@Composable
fun settings_app_version_name(vararg formatArgs: Any): String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ class SettingsViewModel(
initialValue = false
)

val isScreenshotBlockEnabled: StateFlow<Boolean> = interactor.isScreenshotBlockEnabled
.stateIn(
scope = this,
started = SharingStarted.Lazily,
initialValue = false
)

val appVersionData: StateFlow<AppVersionData> = flowOf(AppVersionData(appService.flavor.name))
.stateIn(
scope = this,
Expand Down Expand Up @@ -120,6 +127,10 @@ class SettingsViewModel(
interactor.setBiometricEnabled(enabled)
}

fun setScreenshotBlockEnabled(enabled: Boolean) = launch {
interactor.setScreenshotBlockEnabled(enabled)
}

fun requestReview(activity: Activity) {
reviewService.requestReview(activity)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ internal actual val isTileFeatureEnabled: Boolean
internal actual val isAppIconFeatureEnabled: Boolean
get() = true

internal actual val isScreenshotFeatureEnabled: Boolean
get() = true

internal actual val isGithubFeatureEnabled: Boolean
get() = true

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import org.michaelbel.movies.settings.model.isLanguageFeatureEnabled
import org.michaelbel.movies.settings.model.isMovieListFeatureEnabled
import org.michaelbel.movies.settings.model.isNotificationsFeatureEnabled
import org.michaelbel.movies.settings.model.isReviewAppFeatureEnabled
import org.michaelbel.movies.settings.model.isScreenshotFeatureEnabled
import org.michaelbel.movies.settings.model.isThemeFeatureEnabled
import org.michaelbel.movies.settings.model.isTileFeatureEnabled
import org.michaelbel.movies.settings.model.isUpdateAppFeatureEnabled
Expand Down Expand Up @@ -81,6 +82,7 @@ fun SettingsRoute(
val currentMovieList by viewModel.currentMovieList.collectAsStateWithLifecycle()
val isBiometricFeatureAvailable by viewModel.isBiometricFeatureEnabled.collectAsStateWithLifecycle()
val isBiometricEnabled by viewModel.isBiometricEnabled.collectAsStateWithLifecycle()
val isScreenshotBlockEnabled by viewModel.isScreenshotBlockEnabled.collectAsStateWithLifecycle()
val appVersionData by viewModel.appVersionData.collectAsStateWithLifecycle()

val context = LocalContext.current
Expand Down Expand Up @@ -248,6 +250,11 @@ fun SettingsRoute(
context.setIcon(icon)
}
),
screenshotData = SettingsData.ChangedData(
isFeatureEnabled = isScreenshotFeatureEnabled,
isEnabled = isScreenshotBlockEnabled,
onChange = viewModel::setScreenshotBlockEnabled
),
githubData = SettingsData.RequestedData(
isFeatureEnabled = isGithubFeatureEnabled,
onRequest = { openUrl(resultContract, toolbarColor, MOVIES_GITHUB_URL) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ internal expect val isBiometricFeatureEnabled: Boolean
internal expect val isWidgetFeatureEnabled: Boolean
internal expect val isTileFeatureEnabled: Boolean
internal expect val isAppIconFeatureEnabled: Boolean
internal expect val isScreenshotFeatureEnabled: Boolean
internal expect val isGithubFeatureEnabled: Boolean
internal expect val isReviewAppFeatureEnabled: Boolean
internal expect val isUpdateAppFeatureEnabled: Boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ data class SettingsData(
val widgetData: RequestedData,
val tileData: RequestedData,
val appIconData: ListData<IconAlias>,
val screenshotData: ChangedData,
val githubData: RequestedData,
val reviewAppData: RequestedData,
val updateAppData: RequestedData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,24 @@ internal fun SettingsScreenContent(
}
}
}
if (settingsData.screenshotData.isFeatureEnabled) {
item {
HorizontalDivider(
modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp),
thickness = .1.dp,
color = MaterialTheme.colorScheme.onPrimaryContainer
)
}
item {
SettingSwitchItem(
title = stringResource(MoviesStrings.settings_screenshots),
description = stringResource(MoviesStrings.settings_screenshots_description),
icon = MoviesIcons.Screenshot,
checked = settingsData.screenshotData.isEnabled,
onClick = { settingsData.screenshotData.onChange(!settingsData.screenshotData.isEnabled) }
)
}
}
if (settingsData.githubData.isFeatureEnabled) {
item {
HorizontalDivider(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ internal actual val isTileFeatureEnabled: Boolean
internal actual val isAppIconFeatureEnabled: Boolean
get() = false

internal actual val isScreenshotFeatureEnabled: Boolean
get() = false

internal actual val isGithubFeatureEnabled: Boolean
get() = true

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.michaelbel.movies.settings.model.isLanguageFeatureEnabled
import org.michaelbel.movies.settings.model.isMovieListFeatureEnabled
import org.michaelbel.movies.settings.model.isNotificationsFeatureEnabled
import org.michaelbel.movies.settings.model.isReviewAppFeatureEnabled
import org.michaelbel.movies.settings.model.isScreenshotFeatureEnabled
import org.michaelbel.movies.settings.model.isThemeFeatureEnabled
import org.michaelbel.movies.settings.model.isTileFeatureEnabled
import org.michaelbel.movies.settings.model.isUpdateAppFeatureEnabled
Expand Down Expand Up @@ -102,6 +103,11 @@ fun SettingsRoute(
current = IconAlias.Red,
onSelect = {}
),
screenshotData = SettingsData.ChangedData(
isFeatureEnabled = isScreenshotFeatureEnabled,
isEnabled = false,
onChange = {}
),
githubData = SettingsData.RequestedData(
isFeatureEnabled = isGithubFeatureEnabled,
onRequest = { openUrl(MOVIES_GITHUB_URL) }
Expand Down
Loading

0 comments on commit ecc8b28

Please sign in to comment.