1 /*
<lambda>null2 * 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
17
18 import android.app.Activity
19 import android.content.Intent
20 import android.content.pm.ActivityInfo
21 import android.hardware.Camera
22 import android.net.Uri
23 import android.os.Build
24 import android.os.Bundle
25 import android.provider.MediaStore
26 import android.provider.Settings
27 import android.util.Log
28 import androidx.activity.ComponentActivity
29 import androidx.activity.compose.setContent
30 import androidx.activity.viewModels
31 import androidx.annotation.RequiresApi
32 import androidx.compose.foundation.background
33 import androidx.compose.foundation.isSystemInDarkTheme
34 import androidx.compose.foundation.layout.Arrangement
35 import androidx.compose.foundation.layout.Column
36 import androidx.compose.foundation.layout.fillMaxSize
37 import androidx.compose.foundation.layout.size
38 import androidx.compose.material3.CircularProgressIndicator
39 import androidx.compose.material3.MaterialTheme
40 import androidx.compose.material3.Surface
41 import androidx.compose.material3.Text
42 import androidx.compose.runtime.Composable
43 import androidx.compose.runtime.getValue
44 import androidx.compose.runtime.mutableStateOf
45 import androidx.compose.runtime.setValue
46 import androidx.compose.ui.Alignment
47 import androidx.compose.ui.ExperimentalComposeUiApi
48 import androidx.compose.ui.Modifier
49 import androidx.compose.ui.graphics.Color
50 import androidx.compose.ui.res.stringResource
51 import androidx.compose.ui.semantics.semantics
52 import androidx.compose.ui.semantics.testTagsAsResourceId
53 import androidx.compose.ui.unit.dp
54 import androidx.lifecycle.Lifecycle
55 import androidx.lifecycle.lifecycleScope
56 import androidx.lifecycle.repeatOnLifecycle
57 import com.google.jetpackcamera.MainActivityUiState.Loading
58 import com.google.jetpackcamera.MainActivityUiState.Success
59 import com.google.jetpackcamera.feature.preview.PreviewMode
60 import com.google.jetpackcamera.feature.preview.PreviewViewModel
61 import com.google.jetpackcamera.settings.model.DarkMode
62 import com.google.jetpackcamera.ui.JcaApp
63 import com.google.jetpackcamera.ui.theme.JetpackCameraTheme
64 import dagger.hilt.android.AndroidEntryPoint
65 import kotlinx.coroutines.flow.collect
66 import kotlinx.coroutines.flow.onEach
67 import kotlinx.coroutines.launch
68
69 private const val TAG = "MainActivity"
70
71 /**
72 * Activity for the JetpackCameraApp.
73 */
74 @AndroidEntryPoint(ComponentActivity::class)
75 class MainActivity : Hilt_MainActivity() {
76 private val viewModel: MainActivityViewModel by viewModels()
77
78 @RequiresApi(Build.VERSION_CODES.M)
79 @OptIn(ExperimentalComposeUiApi::class)
80 override fun onCreate(savedInstanceState: Bundle?) {
81 super.onCreate(savedInstanceState)
82 var uiState: MainActivityUiState by mutableStateOf(Loading)
83
84 lifecycleScope.launch {
85 lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
86 viewModel.uiState
87 .onEach {
88 uiState = it
89 }
90 .collect()
91 }
92 }
93 setContent {
94 when (uiState) {
95 Loading -> {
96 Column(
97 modifier = Modifier
98 .fillMaxSize()
99 .background(Color.Black),
100 verticalArrangement = Arrangement.Center,
101 horizontalAlignment = Alignment.CenterHorizontally
102 ) {
103 CircularProgressIndicator(modifier = Modifier.size(50.dp))
104 Text(text = stringResource(R.string.jca_loading), color = Color.White)
105 }
106 }
107
108 is Success -> {
109 // TODO(kimblebee@): add app setting to enable/disable dynamic color
110 JetpackCameraTheme(
111 darkTheme = isInDarkMode(uiState = uiState),
112 dynamicColor = false
113 ) {
114 Surface(
115 modifier = Modifier
116 .fillMaxSize()
117 .semantics {
118 testTagsAsResourceId = true
119 },
120 color = MaterialTheme.colorScheme.background
121 ) {
122 JcaApp(
123 previewMode = getPreviewMode(),
124 openAppSettings = ::openAppSettings,
125 onRequestWindowColorMode = { colorMode ->
126 // Window color mode APIs require API level 26+
127 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
128 Log.d(
129 TAG,
130 "Setting window color mode to:" +
131 " ${colorMode.toColorModeString()}"
132 )
133 window?.colorMode = colorMode
134 }
135 }
136 )
137 }
138 }
139 }
140 }
141 }
142 }
143
144 private fun getPreviewMode(): PreviewMode {
145 if (intent == null || MediaStore.ACTION_IMAGE_CAPTURE != intent.action) {
146 return PreviewMode.StandardMode { event ->
147 if (event is PreviewViewModel.ImageCaptureEvent.ImageSaved) {
148 val intent = Intent(Camera.ACTION_NEW_PICTURE)
149 intent.setData(event.savedUri)
150 sendBroadcast(intent)
151 }
152 }
153 } else {
154 var uri = if (intent.extras == null ||
155 !intent.extras!!.containsKey(MediaStore.EXTRA_OUTPUT)
156 ) {
157 null
158 } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
159 intent.extras!!.getParcelable(
160 MediaStore.EXTRA_OUTPUT,
161 Uri::class.java
162 )
163 } else {
164 @Suppress("DEPRECATION")
165 intent.extras!!.getParcelable(MediaStore.EXTRA_OUTPUT)
166 }
167 if (uri == null && intent.clipData != null && intent.clipData!!.itemCount != 0) {
168 uri = intent.clipData!!.getItemAt(0).uri
169 }
170 return PreviewMode.ExternalImageCaptureMode(uri) { event ->
171 if (event is PreviewViewModel.ImageCaptureEvent.ImageSaved) {
172 setResult(RESULT_OK)
173 finish()
174 }
175 }
176 }
177 }
178 }
179
180 /**
181 * Determines whether the Theme should be in dark, light, or follow system theme
182 */
183 @Composable
isInDarkModenull184 private fun isInDarkMode(uiState: MainActivityUiState): Boolean = when (uiState) {
185 Loading -> isSystemInDarkTheme()
186 is Success -> when (uiState.cameraAppSettings.darkMode) {
187 DarkMode.DARK -> true
188 DarkMode.LIGHT -> false
189 DarkMode.SYSTEM -> isSystemInDarkTheme()
190 }
191 }
192
193 @RequiresApi(Build.VERSION_CODES.O)
toColorModeStringnull194 private fun Int.toColorModeString(): String {
195 return when (this) {
196 ActivityInfo.COLOR_MODE_DEFAULT -> "COLOR_MODE_DEFAULT"
197 ActivityInfo.COLOR_MODE_HDR -> "COLOR_MODE_HDR"
198 ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT -> "COLOR_MODE_WIDE_COLOR_GAMUT"
199 else -> "<Unknown>"
200 }
201 }
202
203 /**
204 * Open the app settings when necessary. I.e. to enable permissions that have been denied by a user
205 */
openAppSettingsnull206 private fun Activity.openAppSettings() {
207 Intent(
208 Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
209 Uri.fromParts("package", packageName, null)
210 ).also(::startActivity)
211 }
212