Skip to content
Draft
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
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ dependencies {

// Access settings & model data
implementation(project(":data:settings"))
implementation(project(":data:settings:api"))

implementation(project(":core:model"))

// Camera Preview
Expand Down
15 changes: 15 additions & 0 deletions app/src/main/java/com/google/jetpackcamera/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@
*/
package com.google.jetpackcamera

import com.google.jetpackcamera.core.common.DefaultAppConfig
import com.google.jetpackcamera.core.common.DefaultCaptureModeOverride
import com.google.jetpackcamera.core.common.DefaultFilePathGenerator
import com.google.jetpackcamera.core.common.DefaultSaveMode
import com.google.jetpackcamera.core.common.FilePathGenerator
import com.google.jetpackcamera.model.CaptureMode
import com.google.jetpackcamera.model.SaveMode
import com.google.jetpackcamera.settings.api.DeveloperAppConfig
import com.google.jetpackcamera.settings.api.OptionRestrictionConfig
import com.google.jetpackcamera.settings.api.SettingConfig
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
Expand All @@ -29,6 +33,17 @@ import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
object AppModule {

@Provides
@DefaultAppConfig
fun providesDeveloperAppConfig(): DeveloperAppConfig = DeveloperAppConfig.LibraryDefaults.copy(
captureMode = SettingConfig(
CaptureMode.IMAGE_ONLY,
uiRestriction = OptionRestrictionConfig.FullyRestricted()
),
hdrEnabled = SettingConfig(defaultValue = true)
)

/**
* provides the default [CaptureMode] to override by the app
*/
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/com/google/jetpackcamera/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ class MainActivity : ComponentActivity() {
JcaApp(
externalCaptureMode = externalCaptureMode,
shouldReviewAfterCapture = shouldReviewAfterCapture,
useDeveloperConfig = useDeveloperConfig,
captureUris = captureUris,
debugSettings = debugSettings,
openAppSettings = ::openAppSettings,
Expand Down Expand Up @@ -211,6 +212,9 @@ class MainActivity : ComponentActivity() {
private val shouldReviewAfterCapture: Boolean
get() = intent?.shouldReviewAfterCapture == true

private val useDeveloperConfig: Boolean
get() = intent?.getBooleanExtra(KEY_USE_DEVELOPER_CONFIG, false) ?: false

private val Intent.externalCaptureUri: Uri?
get() = IntentCompat.getParcelableExtra(
this,
Expand Down Expand Up @@ -310,6 +314,7 @@ class MainActivity : ComponentActivity() {

private const val KEY_DEBUG_MODE = "KEY_DEBUG_MODE"
const val KEY_DEBUG_SINGLE_LENS_MODE = "KEY_DEBUG_SINGLE_LENS_MODE"
const val KEY_USE_DEVELOPER_CONFIG = "KEY_USE_DEVELOPER_CONFIG"
}
}

Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/com/google/jetpackcamera/ui/JcaApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import com.google.jetpackcamera.ui.Routes.SETTINGS_ROUTE
fun JcaApp(
externalCaptureMode: ExternalCaptureMode,
shouldReviewAfterCapture: Boolean,
useDeveloperConfig: Boolean = false,
captureUris: List<Uri>,
debugSettings: DebugSettings,
onRequestWindowColorMode: (Int) -> Unit,
Expand All @@ -63,6 +64,7 @@ fun JcaApp(
modifier = modifier,
externalCaptureMode = externalCaptureMode,
shouldReviewAfterCapture = shouldReviewAfterCapture,
useDeveloperConfig = useDeveloperConfig,
captureUris = captureUris,
debugSettings = debugSettings,
onOpenAppSettings = openAppSettings,
Expand All @@ -78,6 +80,7 @@ private fun JetpackCameraNavHost(
modifier: Modifier = Modifier,
externalCaptureMode: ExternalCaptureMode,
shouldReviewAfterCapture: Boolean,
useDeveloperConfig: Boolean = false,
captureUris: List<Uri>,
debugSettings: DebugSettings,
onOpenAppSettings: () -> Unit,
Expand Down Expand Up @@ -113,6 +116,7 @@ private fun JetpackCameraNavHost(
previewScreen(
externalCaptureMode = externalCaptureMode,
shouldCacheReview = shouldReviewAfterCapture,
useDeveloperConfig = useDeveloperConfig,
captureUris = captureUris,
debugSettings = debugSettings,
onRequestWindowColorMode = onRequestWindowColorMode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,7 @@ annotation class IODispatcher
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class DefaultCoroutineScope

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class DefaultAppConfig
1 change: 1 addition & 0 deletions data/settings/api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
64 changes: 64 additions & 0 deletions data/settings/api/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* Copyright (C) 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
}

android {
namespace = "com.google.jetpackcamera.settings.api"
compileSdk = 35

defaultConfig {
minSdk = 23
targetSdk = 35

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
Comment on lines +43 to +47
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The Java version (11) for this new module is inconsistent with the rest of the project, which uses Java 17 (e.g., in app/build.gradle.kts). It's recommended to keep the toolchain and compatibility versions consistent across all modules to avoid potential build or runtime issues.

        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions {
        jvmTarget = "17"

}
}

dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.appcompat)
implementation(libs.material)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)

// Access Model data
implementation(project(":core:model"))
implementation(project(":core:common"))
implementation(project(":data:settings"))

}
Empty file.
21 changes: 21 additions & 0 deletions data/settings/api/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
19 changes: 19 additions & 0 deletions data/settings/api/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2026 The Android Open Source Project
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright (C) 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.jetpackcamera.settings.api

import com.google.jetpackcamera.model.AspectRatio
import com.google.jetpackcamera.model.CaptureMode
import com.google.jetpackcamera.model.DynamicRange
import com.google.jetpackcamera.model.FlashMode
import com.google.jetpackcamera.model.ImageOutputFormat
import com.google.jetpackcamera.settings.model.CameraAppSettings
import com.google.jetpackcamera.settings.model.DEFAULT_CAMERA_APP_SETTINGS

/**
* Defines a configuration for the Jetpack Camera App that can be used by developers
* to override the default app settings.
*/
data class DeveloperAppConfig(
val captureMode: SettingConfig<CaptureMode>,
val aspectRatio: SettingConfig<AspectRatio>,
val flashMode: SettingConfig<FlashMode>,
val audio: SettingConfig<Boolean>,
val hdrEnabled: SettingConfig<Boolean>
) {
// Ensures that all individual setting configurations are valid.
init {
fun <T : Any> SettingConfig<T>.containsIfOptionsEnabled(options: Set<T>): Boolean {
return when (val restriction = this.uiRestriction) {
is OptionRestrictionConfig.OptionsEnabled -> {
restriction.enabledOptions.containsAll(options)
}

else -> true
}
}

require(flashMode.containsIfOptionsEnabled(setOf(FlashMode.OFF)))
}

companion object {
// Provides a foundation based on JCA's DEFAULT_CAMERA_APP_SETTINGS
val LibraryDefaults: DeveloperAppConfig = DeveloperAppConfig(
aspectRatio = SettingConfig(DEFAULT_CAMERA_APP_SETTINGS.aspectRatio),
flashMode = SettingConfig(DEFAULT_CAMERA_APP_SETTINGS.flashMode),
captureMode = SettingConfig(DEFAULT_CAMERA_APP_SETTINGS.captureMode),
audio = SettingConfig(DEFAULT_CAMERA_APP_SETTINGS.audioEnabled),
hdrEnabled = SettingConfig(
DEFAULT_CAMERA_APP_SETTINGS.dynamicRange != DynamicRange.SDR
)
)
}

/**
* Converts this [DeveloperAppConfig] into a [CameraAppSettings] object.
*
* This function maps the developer-defined settings to the internal camera app settings model.
*/
fun toCameraAppSettings(
defaultSettings: CameraAppSettings = DEFAULT_CAMERA_APP_SETTINGS
): CameraAppSettings {
val imageOutputFormat = if (this.hdrEnabled.defaultValue) {
ImageOutputFormat.JPEG_ULTRA_HDR
} else {
ImageOutputFormat.JPEG
}

val dynamicRange =
if (this.hdrEnabled.defaultValue) DynamicRange.HLG10 else DynamicRange.SDR

return defaultSettings.copy(
aspectRatio = this.aspectRatio.defaultValue,
flashMode = this.flashMode.defaultValue,
captureMode = this.captureMode.defaultValue,
audioEnabled = this.audio.defaultValue,
imageFormat = imageOutputFormat,
dynamicRange = dynamicRange
)
}
}

/**
* Represents a single configurable setting in the application, including its
* default value and any UI restrictions that apply to it.
*
* @param defaultValue The initial value for this setting.
* @param uiRestriction The restrictions applied to this setting in the UI.
*/
data class SettingConfig<T>(
val defaultValue: T,
val uiRestriction: OptionRestrictionConfig<T> = OptionRestrictionConfig.NotRestricted()
) {
init {
// Validate that if options are enabled for this setting, the default value
// is always included in the set of enabled options.
(uiRestriction as? OptionRestrictionConfig.OptionsEnabled)?.let {
require(
uiRestriction.enabledOptions.size >= 2 &&
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The requirement for enabledOptions.size >= 2 seems overly restrictive. There are valid use cases where a developer might want to restrict a setting to exactly one option while still showing it in the UI (e.g., as a disabled selection with a reason). If the intent is to force a single value, a set of size 1 should be allowed.

uiRestriction.enabledOptions.contains(
defaultValue
)
) {
"OptionsRestrictionConfig.OptionsEnabled#enabledOptions must also contain the defaultValue"
}
}
}
}

sealed interface OptionRestrictionConfig<T> {
/** All device-supported options are available. */
class NotRestricted<T> : OptionRestrictionConfig<T>

/** The entire setting is unavailable and hidden from the UI. */
class FullyRestricted<T> : OptionRestrictionConfig<T>

/** ONLY the options in this set are allowed, if supported by the device. */
data class OptionsEnabled<T>(val enabledOptions: Set<T>) : OptionRestrictionConfig<T> {
init {
require(enabledOptions.isNotEmpty()) {
"enabledOptions must not be empty. " +
"Use FullyRestricted to disable the feature."
}
}
}
}
1 change: 1 addition & 0 deletions feature/preview/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ dependencies {
implementation(project(":core:common"))
implementation(project(":data:media"))
implementation(project(":data:settings"))
implementation(project(":data:settings:api"))
implementation(project(":core:model"))
testImplementation(project(":core:common"))
implementation(project(":ui:components:capture"))
Expand Down
Loading
Loading