1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.jetpackcamera.feature.preview 17 18 import android.util.Size 19 import com.google.jetpackcamera.core.camera.VideoRecordingState 20 import com.google.jetpackcamera.feature.preview.ui.ImageWellUiState 21 import com.google.jetpackcamera.feature.preview.ui.SnackbarData 22 import com.google.jetpackcamera.feature.preview.ui.ToastMessage 23 import com.google.jetpackcamera.settings.model.CameraAppSettings 24 import com.google.jetpackcamera.settings.model.CaptureMode 25 import com.google.jetpackcamera.settings.model.FlashMode 26 import com.google.jetpackcamera.settings.model.StabilizationMode 27 import com.google.jetpackcamera.settings.model.SystemConstraints 28 import com.google.jetpackcamera.settings.model.VideoQuality 29 30 /** 31 * Defines the current state of the [PreviewScreen]. 32 */ 33 sealed interface PreviewUiState { 34 data object NotReady : PreviewUiState 35 36 data class Ready( 37 // "quick" settings 38 val currentCameraSettings: CameraAppSettings = CameraAppSettings(), 39 val systemConstraints: SystemConstraints = SystemConstraints(), 40 val zoomScale: Float = 1f, 41 val videoRecordingState: VideoRecordingState = VideoRecordingState.Inactive(), 42 val quickSettingsIsOpen: Boolean = false, 43 44 // todo: remove after implementing post capture screen 45 val toastMessageToShow: ToastMessage? = null, 46 val snackBarToShow: SnackbarData? = null, 47 val lastBlinkTimeStamp: Long = 0, <lambda>null48 val previewMode: PreviewMode = PreviewMode.StandardMode {}, 49 val captureModeToggleUiState: CaptureModeToggleUiState = CaptureModeToggleUiState.Invisible, 50 val sessionFirstFrameTimestamp: Long = 0L, 51 val currentPhysicalCameraId: String? = null, 52 val currentLogicalCameraId: String? = null, 53 val debugUiState: DebugUiState = DebugUiState(), 54 val stabilizationUiState: StabilizationUiState = StabilizationUiState.Disabled, 55 val flashModeUiState: FlashModeUiState = FlashModeUiState.Unavailable, 56 val videoQuality: VideoQuality = VideoQuality.UNSPECIFIED, 57 val audioUiState: AudioUiState = AudioUiState.Disabled, 58 val elapsedTimeUiState: ElapsedTimeUiState = ElapsedTimeUiState.Unavailable, 59 val captureButtonUiState: CaptureButtonUiState = CaptureButtonUiState.Unavailable, 60 val imageWellUiState: ImageWellUiState = ImageWellUiState.NoPreviousCapture 61 ) : PreviewUiState 62 } 63 64 data class DebugUiState( 65 val cameraPropertiesJSON: String = "", 66 val videoResolution: Size? = null, 67 val isDebugMode: Boolean = false, 68 val isDebugOverlayOpen: Boolean = false 69 ) 70 val DEFAULT_CAPTURE_BUTTON_STATE = CaptureButtonUiState.Enabled.Idle(CaptureMode.STANDARD) 71 72 sealed interface CaptureButtonUiState { 73 data object Unavailable : CaptureButtonUiState 74 sealed interface Enabled : CaptureButtonUiState { 75 data class Idle(val captureMode: CaptureMode) : Enabled 76 77 sealed interface Recording : Enabled { 78 data object PressedRecording : Recording 79 data object LockedRecording : Recording 80 } 81 } 82 } 83 sealed interface ElapsedTimeUiState { 84 data object Unavailable : ElapsedTimeUiState 85 86 data class Enabled(val elapsedTimeNanos: Long) : ElapsedTimeUiState 87 } 88 89 sealed interface AudioUiState { 90 val amplitude: Double 91 92 sealed interface Enabled : AudioUiState { 93 data class On(override val amplitude: Double) : Enabled 94 data object Mute : Enabled { 95 override val amplitude = 0.0 96 } 97 } 98 99 // todo give a disabledreason when audio permission is not granted 100 data object Disabled : AudioUiState { 101 override val amplitude = 0.0 102 } 103 } 104 105 sealed interface StabilizationUiState { 106 data object Disabled : StabilizationUiState 107 108 sealed interface Enabled : StabilizationUiState { 109 val stabilizationMode: StabilizationMode 110 val active: Boolean 111 } 112 113 data class Specific( 114 override val stabilizationMode: StabilizationMode, 115 override val active: Boolean = true 116 ) : Enabled { 117 init { <lambda>null118 require(stabilizationMode != StabilizationMode.AUTO) { 119 "Specific StabilizationUiState cannot have AUTO stabilization mode." 120 } 121 } 122 } 123 124 data class Auto(override val stabilizationMode: StabilizationMode) : Enabled { 125 override val active = true 126 } 127 } 128 129 sealed class FlashModeUiState { 130 data object Unavailable : FlashModeUiState() 131 132 data class Available( 133 val selectedFlashMode: FlashMode, 134 val availableFlashModes: List<FlashMode>, 135 val isActive: Boolean 136 ) : FlashModeUiState() { 137 init { <lambda>null138 check(selectedFlashMode in availableFlashModes) { 139 "Selected flash mode of $selectedFlashMode not in available modes: " + 140 "$availableFlashModes" 141 } 142 } 143 } 144 145 companion object { 146 private val ORDERED_UI_SUPPORTED_FLASH_MODES = listOf( 147 FlashMode.OFF, 148 FlashMode.ON, 149 FlashMode.AUTO, 150 FlashMode.LOW_LIGHT_BOOST 151 ) 152 153 /** 154 * Creates a FlashModeUiState from a selected flash mode and a set of supported flash modes 155 * that may not include flash modes supported by the UI. 156 */ createFromnull157 fun createFrom( 158 selectedFlashMode: FlashMode, 159 supportedFlashModes: Set<FlashMode> 160 ): FlashModeUiState { 161 // Ensure we at least support one flash mode 162 check(supportedFlashModes.isNotEmpty()) { 163 "No flash modes supported. Should at least support OFF." 164 } 165 166 // Convert available flash modes to list we support in the UI in our desired order 167 val availableModes = ORDERED_UI_SUPPORTED_FLASH_MODES.filter { 168 it in supportedFlashModes 169 } 170 171 return if (availableModes.isEmpty() || availableModes == listOf(FlashMode.OFF)) { 172 // If we only support OFF, then return "Unavailable". 173 Unavailable 174 } else { 175 Available( 176 selectedFlashMode = selectedFlashMode, 177 availableFlashModes = availableModes, 178 isActive = false 179 ) 180 } 181 } 182 } 183 } 184