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
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.google.ai.sample

import android.content.ContentResolver
import android.provider.Settings

internal object AccessibilityServiceStatusResolver {
fun isServiceEnabled(contentResolver: ContentResolver, packageName: String): Boolean {
val service = "$packageName/${ScreenOperatorAccessibilityService::class.java.canonicalName}"
val enabledServices = Settings.Secure.getString(
contentResolver,
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
)
return enabledServices?.contains(service, ignoreCase = true) == true
}
}
463 changes: 125 additions & 338 deletions app/src/main/kotlin/com/google/ai/sample/MainActivity.kt

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.google.ai.sample

import com.android.billingclient.api.BillingClient

internal object MainActivityBillingClientState {
fun isInitializedAndReady(isInitialized: Boolean, isReady: Boolean): Boolean {
return isInitialized && isReady
}

fun isConnecting(isInitialized: Boolean, connectionState: Int): Boolean {
return isInitialized && connectionState == BillingClient.ConnectionState.CONNECTING
}

fun shouldReconnect(connectionState: Int): Boolean {
return connectionState == BillingClient.ConnectionState.CLOSED ||
connectionState == BillingClient.ConnectionState.DISCONNECTED
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.google.ai.sample

import com.android.billingclient.api.Purchase

internal object MainActivityBillingStateEvaluator {
fun shouldStartTrialService(state: TrialManager.TrialState): Boolean {
return state != TrialManager.TrialState.PURCHASED &&
state != TrialManager.TrialState.EXPIRED_INTERNET_TIME_CONFIRMED
}

fun containsSubscriptionProduct(purchase: Purchase, subscriptionProductId: String): Boolean {
return purchase.products.any { it == subscriptionProductId }
}

fun isPurchasedSubscription(purchase: Purchase, subscriptionProductId: String): Boolean {
return containsSubscriptionProduct(purchase, subscriptionProductId) &&
purchase.purchaseState == Purchase.PurchaseState.PURCHASED
}
}
235 changes: 235 additions & 0 deletions app/src/main/kotlin/com/google/ai/sample/MainActivityDialogs.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
package com.google.ai.sample

import android.util.Log
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog

@Composable
internal fun TrialStateDialogs(
trialState: TrialManager.TrialState,
showTrialInfoDialog: Boolean,
trialInfoMessage: String,
onDismissTrialInfo: () -> Unit,
onPurchaseClick: () -> Unit
) {
when (trialState) {
TrialManager.TrialState.EXPIRED_INTERNET_TIME_CONFIRMED -> {
TrialExpiredDialog(
onPurchaseClick = onPurchaseClick,
onDismiss = {}
)
}

TrialManager.TrialState.NOT_YET_STARTED_AWAITING_INTERNET,
TrialManager.TrialState.INTERNET_UNAVAILABLE_CANNOT_VERIFY -> {
if (showTrialInfoDialog) {
InfoDialog(
message = trialInfoMessage,
onDismiss = onDismissTrialInfo
)
}
}

TrialManager.TrialState.ACTIVE_INTERNET_TIME_CONFIRMED,
TrialManager.TrialState.PURCHASED -> Unit
}
}

@Composable
internal fun PaymentMethodDialog(
onDismiss: () -> Unit,
onPayPalClick: () -> Unit,
onGooglePlayClick: () -> Unit
) {
androidx.compose.material3.AlertDialog(
onDismissRequest = onDismiss,
title = { Text("Choose Payment Method") },
text = {
Column {
Button(
onClick = onPayPalClick,
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)
) {
Text("PayPal (2,60 €/Month)")
}
Button(
onClick = onGooglePlayClick,
modifier = Modifier.fillMaxWidth()
) {
Text("Google Play (2,90 €/Month)")
}
}
},
confirmButton = {},
dismissButton = {
TextButton(onClick = onDismiss) {
Text("Cancel")
}
}
)
}

@Composable
internal fun ApiKeyDialogSection(
apiKeyManager: ApiKeyManager,
isFirstLaunch: Boolean,
initialProvider: ApiProvider?,
onDismiss: () -> Unit
) {
ApiKeyDialog(
apiKeyManager = apiKeyManager,
isFirstLaunch = isFirstLaunch,
initialProvider = initialProvider,
onDismiss = onDismiss
)
}

@Composable
fun FirstLaunchInfoDialog(onDismiss: () -> Unit) {
Log.d("FirstLaunchInfoDialog", "Composing FirstLaunchInfoDialog")
Dialog(onDismissRequest = {
Log.d("FirstLaunchInfoDialog", "onDismissRequest called")
onDismiss()
}) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Trial Information",
style = MaterialTheme.typography.titleLarge
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "You can try Screen Operator for 7 days before you have to subscribe to support the development of more features.",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Spacer(modifier = Modifier.height(24.dp))
TextButton(
onClick = {
Log.d("FirstLaunchInfoDialog", "OK button clicked")
onDismiss()
},
modifier = Modifier.fillMaxWidth()
) {
Text("OK")
}
}
}
}
}



@Composable
fun TrialExpiredDialog(
onPurchaseClick: () -> Unit,
@Suppress("UNUSED_PARAMETER") onDismiss: () -> Unit
) {
Log.d("TrialExpiredDialog", "Composing TrialExpiredDialog")
Dialog(onDismissRequest = {
Log.d("TrialExpiredDialog", "onDismissRequest called (persistent dialog - user tried to dismiss)")
}) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Trial period expired",
style = MaterialTheme.typography.titleLarge
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "Please support the development of the app so that you can continue using it \uD83C\uDF89",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Spacer(modifier = Modifier.height(24.dp))
Button(
onClick = {
Log.d("TrialExpiredDialog", "Purchase button clicked")
onPurchaseClick()
},
modifier = Modifier.fillMaxWidth()
) {
Text("Subscribe")
}
}
}
}
}

@Composable
fun InfoDialog(
message: String,
onDismiss: () -> Unit
) {
Log.d("InfoDialog", "Composing InfoDialog with message: $message")
Dialog(onDismissRequest = {
Log.d("InfoDialog", "onDismissRequest called")
onDismiss()
}) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Information",
style = MaterialTheme.typography.titleMedium
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = message,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.align(Alignment.CenterHorizontally)
)
Spacer(modifier = Modifier.height(24.dp))
TextButton(onClick = {
Log.d("InfoDialog", "OK button clicked")
onDismiss()
}) {
Text("OK")
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.google.ai.sample

import android.content.Context
import android.content.Intent

internal object MainActivityMediaProjectionIntents {
fun startCapture(
context: Context,
resultCode: Int,
resultData: Intent,
takeScreenshotOnStart: Boolean
): Intent {
return Intent(context, ScreenCaptureService::class.java).apply {
action = ScreenCaptureService.ACTION_START_CAPTURE
putExtra(ScreenCaptureService.EXTRA_RESULT_CODE, resultCode)
putExtra(ScreenCaptureService.EXTRA_RESULT_DATA, resultData)
putExtra(ScreenCaptureService.EXTRA_TAKE_SCREENSHOT_ON_START, takeScreenshotOnStart)
}
}

fun keepAliveForWebRtc(context: Context): Intent {
return Intent(context, ScreenCaptureService::class.java).apply {
action = ScreenCaptureService.ACTION_KEEP_ALIVE_FOR_WEBRTC
}
}

fun takeScreenshot(context: Context): Intent {
return Intent(context, ScreenCaptureService::class.java).apply {
action = ScreenCaptureService.ACTION_TAKE_SCREENSHOT
}
}

fun stopCapture(context: Context): Intent {
return Intent(context, ScreenCaptureService::class.java).apply {
action = ScreenCaptureService.ACTION_STOP_CAPTURE
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.google.ai.sample

internal object MainActivityScreenshotFlowDecider {
enum class Action {
TAKE_ADDITIONAL_SCREENSHOT,
REQUEST_PERMISSION
}

fun decide(isScreenCaptureServiceRunning: Boolean): Action {
return if (isScreenCaptureServiceRunning) {
Action.TAKE_ADDITIONAL_SCREENSHOT
} else {
Action.REQUEST_PERMISSION
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.google.ai.sample

import android.content.Intent
import android.net.Uri

internal object MainActivityScreenshotIntents {
fun extractScreenInfo(intent: Intent): String? {
return intent.getStringExtra(MainActivity.EXTRA_SCREEN_INFO)
}

fun extractScreenshotUri(intent: Intent): Uri? {
val uriString = intent.getStringExtra(MainActivity.EXTRA_SCREENSHOT_URI)
return uriString?.let(Uri::parse)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.google.ai.sample

import android.content.Context
import android.util.Log
import android.widget.Toast

internal object MainActivityStatusNotifier {
fun showStatusMessage(context: Context, tag: String, message: String, isError: Boolean) {
Toast.makeText(context, message, if (isError) Toast.LENGTH_LONG else Toast.LENGTH_SHORT).show()
if (isError) {
Log.e(tag, "updateStatusMessage (Error): $message")
} else {
Log.d(tag, "updateStatusMessage (Info): $message")
}
}
}
Loading
Loading