Skip to content
Open
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
Expand Up @@ -17,6 +17,7 @@ package com.android.developers.androidify

import android.os.Build
import android.os.Bundle
import android.view.KeyEvent
import android.view.WindowManager
import android.window.TrustedPresentationThresholds
import androidx.activity.ComponentActivity
Expand All @@ -28,6 +29,7 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import com.android.developers.androidify.camera.HardwareKeyManager
import com.android.developers.androidify.navigation.MainNavigation
import com.android.developers.androidify.theme.AndroidifyTheme
import com.android.developers.androidify.util.LocalOcclusion
Expand Down Expand Up @@ -94,4 +96,12 @@ class MainActivity : ComponentActivity() {
windowManager.unregisterTrustedPresentationListener(presentationListener)
}
}

override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean =
if (HardwareKeyManager.dispatchDown(keyCode, event)) true
else super.onKeyDown(keyCode, event)
Comment on lines +100 to +102
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The implementation of onKeyDown can be made more concise and idiomatic by using the logical OR (||) operator. The current if-else expression is equivalent to HardwareKeyManager.dispatchDown(keyCode, event) || super.onKeyDown(keyCode, event), which is more readable.

    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean =
        HardwareKeyManager.dispatchDown(keyCode, event) || super.onKeyDown(keyCode, event)


override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean =
if (HardwareKeyManager.dispatchUp(keyCode, event)) true
else super.onKeyUp(keyCode, event)
Comment on lines +104 to +106
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Similar to onKeyDown, the implementation of onKeyUp can be made more concise by using the logical OR (||) operator. This improves readability and is more idiomatic in Kotlin.

    override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean =
        HardwareKeyManager.dispatchUp(keyCode, event) || super.onKeyUp(keyCode, event)

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package com.android.developers.androidify.camera

import android.view.KeyEvent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
Expand All @@ -28,7 +29,10 @@ import androidx.compose.material3.ripple
import androidx.compose.material3.toPath
import androidx.compose.material3.toShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithCache
Expand Down Expand Up @@ -86,7 +90,6 @@ internal fun CameraCaptureButton(
} else {
stringResource(R.string.capture_image_button_disabled_content_description)
}

Spacer(
modifier
.indication(interactionSource, ScaleIndicationNodeFactory(animationSpec))
Expand Down Expand Up @@ -146,3 +149,40 @@ internal fun CameraCaptureButton(
},
)
}

@Composable
internal fun RegisterHardwareShutter(
enabled: Boolean,
onCapture: () -> Unit,
) {
val currentCapture by rememberUpdatedState(newValue = onCapture)

DisposableEffect(enabled) {
val token = HardwareKeyManager.register(object : HardwareKeyManager.Handler {
override val priority = 10

private fun isShutterKey(keyCode: Int): Boolean {
return when (keyCode) {
KeyEvent.KEYCODE_VOLUME_UP,
KeyEvent.KEYCODE_VOLUME_DOWN,
KeyEvent.KEYCODE_CAMERA -> true
else -> false
}
}
Comment on lines +164 to +171
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The isShutterKey function can be written more concisely as an expression body, which is a common Kotlin idiom.

            private fun isShutterKey(keyCode: Int): Boolean = when (keyCode) {
                KeyEvent.KEYCODE_VOLUME_UP,
                KeyEvent.KEYCODE_VOLUME_DOWN,
                KeyEvent.KEYCODE_CAMERA -> true
                else -> false
            }


override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (enabled && event.repeatCount == 0 && isShutterKey(keyCode)) {
currentCapture()
return true
}
return false
}

override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
return enabled && isShutterKey(keyCode)
}

})
onDispose { token.close() }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ fun StatelessCameraPreviewContent(
enabled = detectedPose,
captureImageClicked = requestCaptureImage,
)
RegisterHardwareShutter(
enabled = detectedPose,
onCapture = requestCaptureImage,
)
},
flipCameraButton = { flipModifier ->
if (canFlipCamera) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.android.developers.androidify.camera

import android.view.KeyEvent
import java.util.concurrent.CopyOnWriteArrayList
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

This import of java.util.concurrent.CopyOnWriteArrayList is unused and should be removed to improve code clarity. The current thread-safe implementation using a synchronized mutableListOf is correct, so this leftover import can be confusing.


object HardwareKeyManager {
interface Handler {
/** Higher number = higher priority */
val priority: Int get() = 0
fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean = false
fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean = false
}

private val handlers = mutableListOf<Handler>()

fun register(handler: Handler): AutoCloseable {
synchronized(handlers) {
handlers.add(handler)
handlers.sortByDescending { it.priority }
}
return AutoCloseable {
synchronized(handlers) {
handlers.remove(handler)
}
}
}

fun dispatchDown(keyCode: Int, event: KeyEvent): Boolean {
val handlersCopy = synchronized(handlers) { handlers.toList() }
return handlersCopy.any { it.onKeyDown(keyCode, event) }
}

fun dispatchUp(keyCode: Int, event: KeyEvent): Boolean {
val handlersCopy = synchronized(handlers) { handlers.toList() }
return handlersCopy.any { it.onKeyUp(keyCode, event) }
}
}