summaryrefslogtreecommitdiff
path: root/main/src/ui/java
diff options
context:
space:
mode:
authorArne Schwabe <arne@rfc2549.org>2025-11-06 07:41:17 +0100
committerArne Schwabe <arne@rfc2549.org>2025-11-16 01:30:14 +0100
commit5ec86bba571661c70d27368881db797f3a1d926e (patch)
tree00e8ab64bf01c35984283623100fc2fda90438da /main/src/ui/java
parentdda0a2b1eb3ed42fea4c14fea6806600734d1b41 (diff)
Implement minimal UI that allows only connect/disconnect
Diffstat (limited to 'main/src/ui/java')
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/activities/BaseActivity.kt9
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt2
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/activities/MainActivity.kt28
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/activities/VPNPreferences.kt2
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/fragments/MinimalUI.kt168
5 files changed, 201 insertions, 8 deletions
diff --git a/main/src/ui/java/de/blinkt/openvpn/activities/BaseActivity.kt b/main/src/ui/java/de/blinkt/openvpn/activities/BaseActivity.kt
index 9e6dd462..9157c7d2 100644
--- a/main/src/ui/java/de/blinkt/openvpn/activities/BaseActivity.kt
+++ b/main/src/ui/java/de/blinkt/openvpn/activities/BaseActivity.kt
@@ -11,6 +11,7 @@ import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.Window
+import android.widget.Toast
import androidx.activity.SystemBarStyle
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
@@ -19,6 +20,7 @@ import androidx.core.view.WindowInsetsCompat
import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import de.blinkt.openvpn.R
+import de.blinkt.openvpn.core.GlobalPreferences
import de.blinkt.openvpn.core.LocaleHelper
abstract class BaseActivity : AppCompatActivity() {
@@ -28,6 +30,13 @@ abstract class BaseActivity : AppCompatActivity() {
return uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION
}
+ protected fun checkMinimalUIDisabled() {
+ if (GlobalPreferences.getMinimalUi()) {
+ Toast.makeText(this, R.string.minimal_ui_not_available, Toast.LENGTH_LONG).show()
+ finish()
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
if (isAndroidTV) {
requestWindowFeature(Window.FEATURE_OPTIONS_PANEL)
diff --git a/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt b/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt
index 88aa9839..85336e17 100644
--- a/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt
+++ b/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt
@@ -32,6 +32,7 @@ import de.blinkt.openvpn.R
import de.blinkt.openvpn.VpnProfile
import de.blinkt.openvpn.core.ConfigParser
import de.blinkt.openvpn.core.ConfigParser.ConfigParseError
+import de.blinkt.openvpn.core.GlobalPreferences
import de.blinkt.openvpn.core.ProfileManager
import de.blinkt.openvpn.fragments.Utils
import de.blinkt.openvpn.views.FileSelectLayout
@@ -824,6 +825,7 @@ class ConfigConverter : BaseActivity(), FileSelectCallback, View.OnClickListener
override fun onStart() {
super.onStart()
+ checkMinimalUIDisabled()
}
private fun log(logmessage: String?) {
diff --git a/main/src/ui/java/de/blinkt/openvpn/activities/MainActivity.kt b/main/src/ui/java/de/blinkt/openvpn/activities/MainActivity.kt
index a15de114..e941d76c 100644
--- a/main/src/ui/java/de/blinkt/openvpn/activities/MainActivity.kt
+++ b/main/src/ui/java/de/blinkt/openvpn/activities/MainActivity.kt
@@ -14,6 +14,7 @@ import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayoutMediator
import de.blinkt.openvpn.R
+import de.blinkt.openvpn.core.GlobalPreferences
import de.blinkt.openvpn.fragments.*
import de.blinkt.openvpn.fragments.ImportRemoteConfig.Companion.newInstance
import de.blinkt.openvpn.views.ScreenSlidePagerAdapter
@@ -22,6 +23,7 @@ class MainActivity : BaseActivity() {
private lateinit var mPager: ViewPager2
private lateinit var mPagerAdapter: ScreenSlidePagerAdapter
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val view = layoutInflater.inflate(R.layout.main_activity, null)
@@ -32,16 +34,26 @@ class MainActivity : BaseActivity() {
mPagerAdapter = ScreenSlidePagerAdapter(supportFragmentManager, lifecycle, this)
- /* Toolbar and slider should have the same elevation */disableToolbarElevation()
- mPagerAdapter.addTab(R.string.vpn_list_title, VPNProfileList::class.java)
- mPagerAdapter.addTab(R.string.graph, GraphFragment::class.java)
- mPagerAdapter.addTab(R.string.generalsettings, GeneralSettings::class.java)
- mPagerAdapter.addTab(R.string.faq, FaqFragment::class.java)
- if (SendDumpFragment.getLastestDump(this) != null) {
- mPagerAdapter.addTab(R.string.crashdump, SendDumpFragment::class.java)
+ /* Toolbar and slider should have the same elevation */
+ disableToolbarElevation()
+
+ val minimalUi = GlobalPreferences.getMinimalUi();
+ if (minimalUi ) {
+ mPagerAdapter.addTab(R.string.minimal_ui, MinimalUI::class.java)
+ } else {
+
+ mPagerAdapter.addTab(R.string.vpn_list_title, VPNProfileList::class.java)
+ mPagerAdapter.addTab(R.string.graph, GraphFragment::class.java)
+ mPagerAdapter.addTab(R.string.generalsettings, GeneralSettings::class.java)
+ mPagerAdapter.addTab(R.string.faq, FaqFragment::class.java)
+ if (SendDumpFragment.getLastestDump(this) != null) {
+ mPagerAdapter.addTab(R.string.crashdump, SendDumpFragment::class.java)
+ }
+
}
- if (isAndroidTV)
+ if (isAndroidTV || minimalUi)
mPagerAdapter.addTab(R.string.openvpn_log, LogFragment::class.java)
+
mPagerAdapter.addTab(R.string.about, AboutFragment::class.java)
mPager.setAdapter(mPagerAdapter)
diff --git a/main/src/ui/java/de/blinkt/openvpn/activities/VPNPreferences.kt b/main/src/ui/java/de/blinkt/openvpn/activities/VPNPreferences.kt
index a60c7567..91806b5e 100644
--- a/main/src/ui/java/de/blinkt/openvpn/activities/VPNPreferences.kt
+++ b/main/src/ui/java/de/blinkt/openvpn/activities/VPNPreferences.kt
@@ -87,6 +87,8 @@ class VPNPreferences : BaseActivity(), VpnStatus.ProfileNotifyListener {
}
override fun onCreate(savedInstanceState: Bundle?) {
+ checkMinimalUIDisabled()
+
mProfileUUID = intent.getStringExtra("$packageName.profileUUID")
if (savedInstanceState != null) {
val savedUUID = savedInstanceState.getString("$packageName.profileUUID")
diff --git a/main/src/ui/java/de/blinkt/openvpn/fragments/MinimalUI.kt b/main/src/ui/java/de/blinkt/openvpn/fragments/MinimalUI.kt
new file mode 100644
index 00000000..b57fdea7
--- /dev/null
+++ b/main/src/ui/java/de/blinkt/openvpn/fragments/MinimalUI.kt
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2012-2025 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.fragments
+
+import android.Manifest
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.content.pm.PackageManager
+import android.os.Build
+import android.os.Bundle
+import android.os.IBinder
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.CompoundButton
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.fragment.app.Fragment
+import de.blinkt.openvpn.LaunchVPN
+import de.blinkt.openvpn.R
+import de.blinkt.openvpn.VpnProfile
+import de.blinkt.openvpn.core.ConnectionStatus
+import de.blinkt.openvpn.core.IOpenVPNServiceInternal
+import de.blinkt.openvpn.core.OpenVPNService
+import de.blinkt.openvpn.core.ProfileManager
+import de.blinkt.openvpn.core.VpnStatus
+
+class MinimalUI: Fragment(), View.OnClickListener, VpnStatus.StateListener {
+ override fun onClick(v: View?) {
+ TODO("Not yet implemented")
+ }
+
+ private var mPermReceiver: ActivityResultLauncher<String>? = null
+ private lateinit var profileManger: ProfileManager
+ private var mService: IOpenVPNServiceInternal? = null
+ private lateinit var vpnstatus: TextView
+ private lateinit var vpntoggle: CompoundButton
+
+ private lateinit var view: View
+
+ private val mConnection: ServiceConnection = object : ServiceConnection {
+ override fun onServiceConnected(
+ className: ComponentName,
+ service: IBinder
+ ) {
+ mService = IOpenVPNServiceInternal.Stub.asInterface(service)
+ }
+
+ override fun onServiceDisconnected(arg0: ComponentName) {
+ mService = null
+ }
+ }
+
+ private fun registerPermissionReceiver() {
+ mPermReceiver = registerForActivityResult(
+ ActivityResultContracts.RequestPermission()
+ ) { result: Boolean? ->
+ checkForNotificationPermission(
+ requireView()
+ )
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ registerPermissionReceiver()
+
+ profileManger = ProfileManager.getInstance(requireContext());
+ }
+
+ override fun onResume() {
+ super.onResume()
+ VpnStatus.addStateListener(this)
+
+ val intent = Intent(requireActivity(), OpenVPNService::class.java)
+ intent.action = OpenVPNService.START_SERVICE
+ requireActivity().bindService(intent, mConnection, Context.BIND_AUTO_CREATE)
+
+ /* Check the default VPN */
+ val alwaysOnVPN: VpnProfile? = ProfileManager.getAlwaysOnVPN(requireContext())
+ if (alwaysOnVPN == null) {
+ vpnstatus.text = "Default VPN is not configured."
+ }
+ }
+
+ override fun onPause() {
+ super.onPause()
+ VpnStatus.removeStateListener(this)
+
+ requireActivity().unbindService(mConnection)
+ }
+
+ private fun checkForNotificationPermission(v: View) {
+ val permissionView = v.findViewById<View>(R.id.notification_permission)
+ val permissionGranted =
+ requireActivity().checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
+ permissionView.setVisibility(if (permissionGranted) View.GONE else View.VISIBLE)
+ permissionView.setOnClickListener({ view: View? ->
+ mPermReceiver?.launch(
+ Manifest.permission.POST_NOTIFICATIONS
+ )
+ })
+ }
+
+ override fun updateState(
+ state: String?,
+ logmessage: String?,
+ localizedResId: Int,
+ level: ConnectionStatus?,
+ Intent: Intent?
+ ) {
+ val cleanLogMessage = VpnStatus.getLastCleanLogMessage(activity)
+
+ requireActivity().runOnUiThread {
+ vpnstatus.setText(localizedResId)
+ val connected = level == ConnectionStatus.LEVEL_CONNECTED;
+ vpntoggle.isChecked = connected
+ }
+ }
+
+ override fun setConnectedVPN(uuid: String?) {
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ view = inflater.inflate(R.layout.minimalui, container, false)
+ vpntoggle = view.findViewById(R.id.vpntoggle)
+ vpnstatus = view.findViewById(R.id.vpnstatus)
+
+ vpntoggle.setOnClickListener { view ->
+ toggleSwitchPressed(view as CompoundButton)
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
+ checkForNotificationPermission(view)
+ return view
+ }
+
+ fun toggleSwitchPressed(view: CompoundButton) {
+
+ val alwaysOnVPN = ProfileManager.getAlwaysOnVPN(requireContext())
+ if (alwaysOnVPN == null) {
+ Toast.makeText(
+ requireContext(),
+ R.string.cannot_start_vpn_not_configured,
+ Toast.LENGTH_SHORT
+ ).show();
+ view.setChecked(false)
+ return
+ }
+ val intent = Intent(requireContext(), LaunchVPN::class.java)
+ intent.putExtra(LaunchVPN.EXTRA_KEY, alwaysOnVPN.uuidString)
+ intent.putExtra(OpenVPNService.EXTRA_START_REASON, "VPN started from homescreen.")
+ intent.action = Intent.ACTION_MAIN
+ startActivity(intent)
+ }
+
+} \ No newline at end of file