Skip to content

Commit 5ec86bb

Browse files
committed
Implement minimal UI that allows only connect/disconnect
1 parent dda0a2b commit 5ec86bb

File tree

12 files changed

+344
-8
lines changed

12 files changed

+344
-8
lines changed

main/src/main/java/de/blinkt/openvpn/api/AppRestrictions.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import de.blinkt.openvpn.VpnProfile;
1616
import de.blinkt.openvpn.core.ConfigParser;
17+
import de.blinkt.openvpn.core.GlobalPreferences;
1718
import de.blinkt.openvpn.core.Preferences;
1819
import de.blinkt.openvpn.core.ProfileManager;
1920
import de.blinkt.openvpn.core.VpnStatus;
@@ -91,6 +92,7 @@ private void applyRestrictions(Context c) {
9192
Bundle restrictions = restrictionsMgr.getApplicationRestrictions();
9293
parseRestrictionsBundle(c, restrictions);
9394
}
95+
9496
public void parseRestrictionsBundle(Context c, Bundle restrictions)
9597
{
9698
if (restrictions == null)
@@ -169,6 +171,11 @@ private static void setMiscSettings(Context c, Bundle restrictions) {
169171
editor.putBoolean("restartvpnonboot", restartVPNonBoot);
170172
editor.apply();
171173
}
174+
175+
176+
boolean minimalUi = restrictions.getBoolean("minimal_ui", false);
177+
boolean forceConnected = restrictions.getBoolean("always_connected", false);
178+
GlobalPreferences.setInstance(minimalUi, forceConnected);
172179
}
173180

174181
private void importVPNProfiles(Context c, Bundle restrictions, Parcelable[] profileList) {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) 2012-2025 Arne Schwabe
3+
* Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4+
*/
5+
6+
package de.blinkt.openvpn.core;
7+
8+
/* This class is a data holder for the global preferences that are set when reading the app restrictions */
9+
public class GlobalPreferences {
10+
boolean minimalUi = false;
11+
boolean forceConnected = false;
12+
13+
/* will be set by AppRestrictions */
14+
static GlobalPreferences instance = null;
15+
16+
GlobalPreferences(boolean minimalUi, boolean forceConnected)
17+
{
18+
this.minimalUi = minimalUi;
19+
this.forceConnected = forceConnected;
20+
}
21+
22+
public static void setInstance(boolean minimalUi, boolean forceConnected)
23+
{
24+
instance = new GlobalPreferences(minimalUi, forceConnected);
25+
}
26+
27+
static public boolean getMinimalUi()
28+
{
29+
return getInstance().minimalUi;
30+
}
31+
32+
static public boolean getForceConnected()
33+
{
34+
return getInstance().forceConnected;
35+
}
36+
37+
static GlobalPreferences getInstance()
38+
{
39+
if (instance == null)
40+
throw new RuntimeException("Global preferences instance is not set");
41+
42+
return instance;
43+
}
44+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
~ Copyright (c) 2012-2025 Arne Schwabe
4+
~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
5+
-->
6+
7+
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
8+
<item android:drawable="@drawable/ic_logo_bunt"
9+
android:state_checked="true"/>
10+
<item android:drawable="@drawable/ic_logo_sw"
11+
android:state_checked="false"/>
12+
<item android:drawable="@drawable/ic_stat_vpn_offline"/>
13+
</selector>
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?xml version="1.0" encoding="utf-8"?><!--
2+
~ Copyright (c) 2012-2023 Arne Schwabe
3+
~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
4+
-->
5+
6+
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
7+
xmlns:app="http://schemas.android.com/apk/res-auto"
8+
xmlns:tools="http://schemas.android.com/tools"
9+
android:layout_width="match_parent"
10+
android:layout_height="match_parent"
11+
android:padding="20sp">
12+
13+
<TextView
14+
android:id="@+id/notification_permission"
15+
android:layout_width="match_parent"
16+
android:layout_height="wrap_content"
17+
android:padding="@dimen/stdpadding"
18+
android:drawableStart="@drawable/notifications"
19+
android:drawablePadding="@dimen/stdpadding"
20+
android:text="@string/missing_notification_permission"
21+
android:visibility="gone"
22+
app:layout_constraintEnd_toEndOf="parent"
23+
24+
app:layout_constraintStart_toStartOf="parent"
25+
app:layout_constraintTop_toTopOf="parent"
26+
tools:visibility="visible" />
27+
28+
<CheckBox
29+
android:id="@+id/vpntoggle"
30+
android:layout_width="wrap_content"
31+
android:layout_height="wrap_content"
32+
android:button="@drawable/vpn_status_indicator"
33+
android:scaleX="3"
34+
android:scaleY="3"
35+
android:text=""
36+
android:textSize="45sp"
37+
app:layout_constraintBottom_toTopOf="@+id/vpnstatus"
38+
app:layout_constraintEnd_toEndOf="parent"
39+
app:layout_constraintHorizontal_bias="0.5"
40+
app:layout_constraintStart_toStartOf="parent"
41+
app:layout_constraintTop_toBottomOf="@+id/notification_permission"
42+
app:switchMinWidth="80dp" />
43+
44+
<TextView
45+
android:id="@+id/vpnstatus"
46+
android:layout_width="wrap_content"
47+
android:layout_height="wrap_content"
48+
android:textSize="25sp"
49+
app:layout_constraintBottom_toBottomOf="parent"
50+
app:layout_constraintEnd_toEndOf="parent"
51+
app:layout_constraintHorizontal_bias="0.5"
52+
app:layout_constraintStart_toStartOf="parent"
53+
app:layout_constraintTop_toBottomOf="@+id/vpntoggle"
54+
tools:text="VPN connection status text" />
55+
56+
</androidx.constraintlayout.widget.ConstraintLayout>

main/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<string name="copyright_others">This program uses the following components; see the source code for full details on the licenses</string>
2323
<string name="about">About</string>
2424
<string name="vpn_list_title">Profiles</string>
25+
<string name="minimal_ui">Minimal UI</string>
2526
<string name="vpn_type">Type</string>
2627
<string name="pkcs12pwquery">PKCS12 Password</string>
2728
<string name="file_select">Select…</string>
@@ -521,5 +522,7 @@
521522
<string name="editor_close_profile_changed">Profile has changed outside the editor. Closing configuration editor.</string>
522523
<string name="confirmations_title">Disable VPN connection confirmations</string>
523524
<string name="confirmations_summary">(Not recommended) Disables confirmations when connecting/disconnecting VPN. This disables the safeguard to accidientially disconnect VPN connections</string>
525+
<string name="cannot_start_vpn_not_configured">Cannot start VPN. No default VPN has been configured.</string>
526+
<string name="minimal_ui_not_available">Functionality not available when minimal UI is enabled</string>
524527

525528
</resources>

main/src/main/res/values/untranslatable.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@
9191
<string name="restriction_ignorenetworkstate">Keep the VPN connected even when no network is detected, e.g. when reverse tethering over USB using adb</string>
9292
<string name="apprest_aidl_list">List of apps that are allowed to use the remote AIDL. If this list is in the restrictions, the app will not allowed any changes to the list by the user. Package names of allowed apps separated by comma, space or newlines</string>
9393
<string name="apprest_remoteaidl">Remote API access</string>
94+
<string name="apprest_minimal_ui">Only present a restricted UI that only allow connecting/disconnecting the default VPN. Most other functionality is is disabled as well</string>
95+
<string name="apprest_minimal_ui_title">Minimal UI</string>
96+
<string name="apprest_always_connected">When in the minimal UI mode, try to keep the default VPN always connected. Do not allow disconnecting it. Restarting the VPN is still allowed.</string>
97+
<string name="apprest_always_connected_title">Disallow disconnecting default VPN</string>
98+
9499

95100
<string-array name="tls_profile_values" translatable="false">
96101
<item>insecure</item>

main/src/main/res/xml/app_restrictions.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,19 @@
9191
android:key="allowed_remote_access"
9292
android:restrictionType="string"
9393
android:title="@string/apprest_remoteaidl" />
94+
95+
<restriction
96+
android:defaultValue="false"
97+
android:description="@string/apprest_minimal_ui"
98+
android:key="minimal_ui"
99+
android:restrictionType="bool"
100+
android:title="@string/apprest_minimal_ui_title" />
101+
102+
<restriction
103+
android:defaultValue="false"
104+
android:description="@string/apprest_always_connected"
105+
android:key="always_connected"
106+
android:restrictionType="bool"
107+
android:title="@string/apprest_always_connected_title" />
108+
94109
</restrictions>

main/src/ui/java/de/blinkt/openvpn/activities/BaseActivity.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import android.os.Bundle
1111
import android.view.View
1212
import android.view.ViewGroup
1313
import android.view.Window
14+
import android.widget.Toast
1415
import androidx.activity.SystemBarStyle
1516
import androidx.activity.enableEdgeToEdge
1617
import androidx.appcompat.app.AppCompatActivity
@@ -19,6 +20,7 @@ import androidx.core.view.WindowInsetsCompat
1920
import androidx.core.view.updateLayoutParams
2021
import androidx.core.view.updatePadding
2122
import de.blinkt.openvpn.R
23+
import de.blinkt.openvpn.core.GlobalPreferences
2224
import de.blinkt.openvpn.core.LocaleHelper
2325

2426
abstract class BaseActivity : AppCompatActivity() {
@@ -28,6 +30,13 @@ abstract class BaseActivity : AppCompatActivity() {
2830
return uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION
2931
}
3032

33+
protected fun checkMinimalUIDisabled() {
34+
if (GlobalPreferences.getMinimalUi()) {
35+
Toast.makeText(this, R.string.minimal_ui_not_available, Toast.LENGTH_LONG).show()
36+
finish()
37+
}
38+
}
39+
3140
override fun onCreate(savedInstanceState: Bundle?) {
3241
if (isAndroidTV) {
3342
requestWindowFeature(Window.FEATURE_OPTIONS_PANEL)

main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import de.blinkt.openvpn.R
3232
import de.blinkt.openvpn.VpnProfile
3333
import de.blinkt.openvpn.core.ConfigParser
3434
import de.blinkt.openvpn.core.ConfigParser.ConfigParseError
35+
import de.blinkt.openvpn.core.GlobalPreferences
3536
import de.blinkt.openvpn.core.ProfileManager
3637
import de.blinkt.openvpn.fragments.Utils
3738
import de.blinkt.openvpn.views.FileSelectLayout
@@ -824,6 +825,7 @@ class ConfigConverter : BaseActivity(), FileSelectCallback, View.OnClickListener
824825

825826
override fun onStart() {
826827
super.onStart()
828+
checkMinimalUIDisabled()
827829
}
828830

829831
private fun log(logmessage: String?) {

main/src/ui/java/de/blinkt/openvpn/activities/MainActivity.kt

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import androidx.viewpager2.widget.ViewPager2
1414
import com.google.android.material.tabs.TabLayout
1515
import com.google.android.material.tabs.TabLayoutMediator
1616
import de.blinkt.openvpn.R
17+
import de.blinkt.openvpn.core.GlobalPreferences
1718
import de.blinkt.openvpn.fragments.*
1819
import de.blinkt.openvpn.fragments.ImportRemoteConfig.Companion.newInstance
1920
import de.blinkt.openvpn.views.ScreenSlidePagerAdapter
@@ -22,6 +23,7 @@ class MainActivity : BaseActivity() {
2223
private lateinit var mPager: ViewPager2
2324
private lateinit var mPagerAdapter: ScreenSlidePagerAdapter
2425

26+
2527
override fun onCreate(savedInstanceState: Bundle?) {
2628
super.onCreate(savedInstanceState)
2729
val view = layoutInflater.inflate(R.layout.main_activity, null)
@@ -32,16 +34,26 @@ class MainActivity : BaseActivity() {
3234

3335
mPagerAdapter = ScreenSlidePagerAdapter(supportFragmentManager, lifecycle, this)
3436

35-
/* Toolbar and slider should have the same elevation */disableToolbarElevation()
36-
mPagerAdapter.addTab(R.string.vpn_list_title, VPNProfileList::class.java)
37-
mPagerAdapter.addTab(R.string.graph, GraphFragment::class.java)
38-
mPagerAdapter.addTab(R.string.generalsettings, GeneralSettings::class.java)
39-
mPagerAdapter.addTab(R.string.faq, FaqFragment::class.java)
40-
if (SendDumpFragment.getLastestDump(this) != null) {
41-
mPagerAdapter.addTab(R.string.crashdump, SendDumpFragment::class.java)
37+
/* Toolbar and slider should have the same elevation */
38+
disableToolbarElevation()
39+
40+
val minimalUi = GlobalPreferences.getMinimalUi();
41+
if (minimalUi ) {
42+
mPagerAdapter.addTab(R.string.minimal_ui, MinimalUI::class.java)
43+
} else {
44+
45+
mPagerAdapter.addTab(R.string.vpn_list_title, VPNProfileList::class.java)
46+
mPagerAdapter.addTab(R.string.graph, GraphFragment::class.java)
47+
mPagerAdapter.addTab(R.string.generalsettings, GeneralSettings::class.java)
48+
mPagerAdapter.addTab(R.string.faq, FaqFragment::class.java)
49+
if (SendDumpFragment.getLastestDump(this) != null) {
50+
mPagerAdapter.addTab(R.string.crashdump, SendDumpFragment::class.java)
51+
}
52+
4253
}
43-
if (isAndroidTV)
54+
if (isAndroidTV || minimalUi)
4455
mPagerAdapter.addTab(R.string.openvpn_log, LogFragment::class.java)
56+
4557
mPagerAdapter.addTab(R.string.about, AboutFragment::class.java)
4658
mPager.setAdapter(mPagerAdapter)
4759

0 commit comments

Comments
 (0)