Warning, /network/kdeconnect-android/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterActivity.kt is written in an unsupported language. File is not indexed.
0001 /*
0002 * SPDX-FileCopyrightText: 2023 Dmitry Yudin <dgyudin@gmail.com>
0003 *
0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006
0007 package org.kde.kdeconnect.Plugins.PresenterPlugin
0008
0009 import android.hardware.Sensor
0010 import android.hardware.SensorEvent
0011 import android.hardware.SensorEventListener
0012 import android.hardware.SensorManager
0013 import android.os.Build
0014 import android.os.Bundle
0015 import android.os.PowerManager
0016 import android.support.v4.media.session.MediaSessionCompat
0017 import android.support.v4.media.session.PlaybackStateCompat
0018 import android.view.MotionEvent
0019 import androidx.activity.compose.setContent
0020 import androidx.appcompat.app.AppCompatActivity
0021 import androidx.compose.foundation.layout.*
0022 import androidx.compose.material.icons.Icons
0023 import androidx.compose.material.icons.filled.ArrowBack
0024 import androidx.compose.material.icons.filled.ArrowForward
0025 import androidx.compose.material.icons.filled.MoreVert
0026 import androidx.compose.material3.*
0027 import androidx.compose.runtime.*
0028 import androidx.compose.ui.ExperimentalComposeUiApi
0029 import androidx.compose.ui.Modifier
0030 import androidx.compose.ui.input.pointer.pointerInteropFilter
0031 import androidx.compose.ui.platform.LocalContext
0032 import androidx.compose.ui.res.stringResource
0033 import androidx.compose.ui.tooling.preview.Preview
0034 import androidx.compose.ui.unit.dp
0035 import androidx.media.VolumeProviderCompat
0036 import com.google.accompanist.themeadapter.material3.Mdc3Theme
0037 import org.kde.kdeconnect.KdeConnect
0038 import org.kde.kdeconnect.UserInterface.compose.KdeButton
0039 import org.kde.kdeconnect.UserInterface.compose.KdeTopAppBar
0040 import org.kde.kdeconnect_tp.R
0041
0042 private const val VOLUME_UP = 1
0043 private const val VOLUME_DOWN = -1
0044
0045 class PresenterActivity : AppCompatActivity(), SensorEventListener {
0046
0047 private val offScreenControlsSupported = Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU
0048 private val mediaSession by lazy {
0049 if (offScreenControlsSupported) MediaSessionCompat(this, "kdeconnect") else null
0050 }
0051 private val powerManager by lazy { getSystemService(POWER_SERVICE) as PowerManager }
0052 private val plugin: PresenterPlugin by lazy {
0053 KdeConnect.getInstance().getDevicePlugin(intent.getStringExtra("deviceId"), PresenterPlugin::class.java)
0054 }
0055
0056 //TODO: make configurable
0057 private val sensitivity = 0.03f
0058 override fun onSensorChanged(event: SensorEvent?) {
0059 if (event?.sensor?.type == Sensor.TYPE_GYROSCOPE) {
0060 val xPos = -event.values[2] * sensitivity
0061 val yPos = -event.values[0] * sensitivity
0062
0063 plugin.sendPointer(xPos, yPos)
0064 }
0065 }
0066
0067 override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {
0068 //ignored
0069 }
0070
0071 override fun onCreate(savedInstanceState: Bundle?) {
0072 super.onCreate(savedInstanceState)
0073 setContent { PresenterScreen() }
0074 createMediaSession()
0075 }
0076
0077 override fun onResume() {
0078 super.onResume()
0079 mediaSession?.setActive(false)
0080 }
0081
0082 override fun onPause() {
0083 super.onPause()
0084 //fixme watch for isInteractive in background
0085 mediaSession?.setActive(!powerManager.isInteractive)
0086 }
0087
0088 override fun onDestroy() {
0089 mediaSession?.release()
0090 super.onDestroy()
0091 }
0092
0093 private fun createMediaSession() {
0094 mediaSession?.setPlaybackState(
0095 PlaybackStateCompat.Builder().setState(PlaybackStateCompat.STATE_PLAYING, 0, 0f).build()
0096 )
0097 mediaSession?.setPlaybackToRemote(volumeProvider)
0098
0099 }
0100
0101 private val volumeProvider = object : VolumeProviderCompat(VOLUME_CONTROL_RELATIVE, 1, 0) {
0102 override fun onAdjustVolume(direction: Int) {
0103 if (direction == VOLUME_UP) {
0104 plugin.sendNext()
0105 } else if (direction == VOLUME_DOWN) {
0106 plugin.sendPrevious()
0107 }
0108 }
0109 }
0110
0111 @OptIn(ExperimentalComposeUiApi::class)
0112 @Preview
0113 @Composable
0114 private fun PresenterScreen() {
0115
0116 val sensorManager = LocalContext.current.getSystemService(SENSOR_SERVICE) as? SensorManager
0117
0118 Mdc3Theme {
0119 Scaffold(topBar = { PresenterAppBar() }) {
0120 Column(
0121 modifier = Modifier.fillMaxSize().padding(it).padding(16.dp),
0122 verticalArrangement = Arrangement.spacedBy(20.dp),
0123 ) {
0124 if (offScreenControlsSupported) Text(
0125 text = stringResource(R.string.presenter_lock_tip),
0126 modifier = Modifier.padding(bottom = 8.dp).padding(horizontal = 16.dp),
0127 style = MaterialTheme.typography.bodyLarge,
0128 )
0129 Row(
0130 modifier = Modifier.fillMaxSize().weight(3f),
0131 horizontalArrangement = Arrangement.spacedBy(20.dp),
0132 ) {
0133 KdeButton(
0134 onClick = { plugin.sendPrevious() },
0135 modifier = Modifier.fillMaxSize().weight(1f),
0136 icon = Icons.Default.ArrowBack,
0137 )
0138 KdeButton(
0139 onClick = { plugin.sendNext() },
0140 modifier = Modifier.fillMaxSize().weight(1f),
0141 icon = Icons.Default.ArrowForward,
0142 )
0143 }
0144 if (sensorManager != null) KdeButton(
0145 onClick = {},
0146 colors = ButtonDefaults.filledTonalButtonColors(),
0147 modifier = Modifier.fillMaxSize().weight(1f).pointerInteropFilter { event ->
0148 when (event.action) {
0149 MotionEvent.ACTION_DOWN -> {
0150 sensorManager.registerListener(
0151 this@PresenterActivity,
0152 sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE),
0153 SensorManager.SENSOR_DELAY_GAME
0154 )
0155 }
0156
0157 MotionEvent.ACTION_UP -> {
0158 sensorManager.unregisterListener(this@PresenterActivity)
0159 plugin.stopPointer()
0160 false
0161 }
0162
0163 else -> false
0164 }
0165 },
0166 text = stringResource(R.string.presenter_pointer),
0167 )
0168 }
0169 }
0170 }
0171 }
0172
0173 @Preview
0174 @Composable
0175 private fun PresenterAppBar() {
0176
0177 var dropdownShownState by remember { mutableStateOf(false) }
0178
0179 KdeTopAppBar(navIconOnClick = { onBackPressedDispatcher.onBackPressed() }, actions = {
0180 IconButton(onClick = { dropdownShownState = true }) {
0181 Icon(Icons.Default.MoreVert, stringResource(R.string.extra_options))
0182 }
0183 DropdownMenu(expanded = dropdownShownState, onDismissRequest = { dropdownShownState = false }) {
0184 DropdownMenuItem(
0185 onClick = { plugin.sendFullscreen() },
0186 text = { Text(stringResource(R.string.presenter_fullscreen)) },
0187 )
0188 DropdownMenuItem(
0189 onClick = { plugin.sendEsc() },
0190 text = { Text(stringResource(R.string.presenter_exit)) },
0191 )
0192 }
0193 })
0194 }
0195 }