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 }