• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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