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 }