Browse Source

Merge pull request #12884 from nextcloud/feature/app-config

App Config With Restriction Manager
Tobias Kaminsky 1 year ago
parent
commit
176a09615c

+ 86 - 0
app/src/androidTest/java/com/nextcloud/utils/AppConfigManagerTests.kt

@@ -0,0 +1,86 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.nextcloud.utils
+
+import android.os.Bundle
+import com.owncloud.android.AbstractIT
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
+import com.owncloud.android.utils.appConfig.AppConfigKeys
+import com.owncloud.android.utils.appConfig.AppConfigManager
+import org.junit.AfterClass
+import org.junit.Test
+
+class AppConfigManagerTests : AbstractIT() {
+
+    private val testBaseUrl = "nextcloud.cloud.cloud"
+    private val testProxyHost = "nextcloud.cloud.cloud.com"
+
+    @Suppress("MagicNumber")
+    private val testProxyPort = 8800
+
+    @Test
+    fun testSetProxyConfigWhenGivenClientBrandedPlusAndCorrectBundleDataProxyConfigurationShouldSet() {
+        val proxySetting = Bundle().apply {
+            putString(AppConfigKeys.ProxyHost.key, testProxyHost)
+            putInt(AppConfigKeys.ProxyPort.key, testProxyPort)
+        }
+
+        AppConfigManager(targetContext, proxySetting).run {
+            setProxyConfig(true)
+        }
+
+        val proxyHost = OwnCloudClientManagerFactory.getProxyHost()
+        val proxyPort = OwnCloudClientManagerFactory.getProxyPort()
+
+        assert(proxyHost.equals(testProxyHost))
+        assert(proxyPort == testProxyPort)
+    }
+
+    @Test
+    fun testSetProxyConfigWhenGivenClientNotBrandedPlusAndCorrectBundleDataProxyConfigurationShouldNotSet() {
+        val proxySetting = Bundle().apply {
+            putString(AppConfigKeys.ProxyHost.key, testProxyHost)
+            putInt(AppConfigKeys.ProxyPort.key, testProxyPort)
+        }
+
+        AppConfigManager(targetContext, proxySetting).run {
+            setProxyConfig(false)
+        }
+
+        val proxyHost = OwnCloudClientManagerFactory.getProxyHost()
+        val proxyPort = OwnCloudClientManagerFactory.getProxyPort()
+
+        assert(proxyHost.equals(""))
+        assert(proxyPort == -1)
+    }
+
+    @Test
+    fun testGetBaseUrlConfigWhenGivenClientBrandedPlusAndCorrectBundleDataBaseUrlConfigurationShouldSet() {
+        val baseUrlConfig = Bundle().apply {
+            putString(AppConfigKeys.BaseUrl.key, testBaseUrl)
+        }
+        val sut = AppConfigManager(targetContext, baseUrlConfig)
+        assert(!sut.getBaseUrl(true).isNullOrEmpty())
+    }
+
+    @Test
+    fun testGetBaseUrlConfigWhenGivenClientBrandedPlusAndBrokenBundleDataBaseUrlConfigurationShouldNotSet() {
+        val baseUrlConfig = Bundle()
+        val sut = AppConfigManager(targetContext, baseUrlConfig)
+        assert(sut.getBaseUrl(true).isNullOrEmpty())
+    }
+
+    companion object {
+        @JvmStatic
+        @AfterClass
+        fun tearDown() {
+            OwnCloudClientManagerFactory.setProxyHost("")
+            OwnCloudClientManagerFactory.setProxyPort(-1)
+        }
+    }
+}

+ 4 - 0
app/src/main/AndroidManifest.xml

@@ -126,6 +126,10 @@
         android:usesCleartextTraffic="true"
         android:usesCleartextTraffic="true"
         tools:ignore="UnusedAttribute"
         tools:ignore="UnusedAttribute"
         tools:replace="android:allowBackup">
         tools:replace="android:allowBackup">
+
+        <meta-data android:name="android.content.APP_RESTRICTIONS"
+            android:resource="@xml/app_config" />
+
         <activity
         <activity
             android:name="com.nextcloud.ui.composeActivity.ComposeActivity"
             android:name="com.nextcloud.ui.composeActivity.ComposeActivity"
             android:exported="false" />
             android:exported="false" />

+ 44 - 5
app/src/main/java/com/owncloud/android/MainApp.java

@@ -21,9 +21,12 @@ import android.app.ActivityManager;
 import android.app.Application;
 import android.app.Application;
 import android.app.NotificationChannel;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Context;
 import android.content.Intent;
 import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.RestrictionsManager;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager;
@@ -55,6 +58,7 @@ import com.nextcloud.client.onboarding.OnboardingService;
 import com.nextcloud.client.preferences.AppPreferences;
 import com.nextcloud.client.preferences.AppPreferences;
 import com.nextcloud.client.preferences.AppPreferencesImpl;
 import com.nextcloud.client.preferences.AppPreferencesImpl;
 import com.nextcloud.client.preferences.DarkMode;
 import com.nextcloud.client.preferences.DarkMode;
+import com.nextcloud.utils.extensions.ContextExtensionsKt;
 import com.nmc.android.ui.LauncherActivity;
 import com.nmc.android.ui.LauncherActivity;
 import com.owncloud.android.authentication.AuthenticatorActivity;
 import com.owncloud.android.authentication.AuthenticatorActivity;
 import com.owncloud.android.authentication.PassCodeManager;
 import com.owncloud.android.authentication.PassCodeManager;
@@ -63,6 +67,7 @@ import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
 import com.owncloud.android.datamodel.MediaFolder;
 import com.owncloud.android.datamodel.MediaFolder;
 import com.owncloud.android.datamodel.MediaFolderType;
 import com.owncloud.android.datamodel.MediaFolderType;
 import com.owncloud.android.datamodel.MediaProvider;
 import com.owncloud.android.datamodel.MediaProvider;
+import com.owncloud.android.datamodel.ReceiverFlag;
 import com.owncloud.android.datamodel.SyncedFolder;
 import com.owncloud.android.datamodel.SyncedFolder;
 import com.owncloud.android.datamodel.SyncedFolderProvider;
 import com.owncloud.android.datamodel.SyncedFolderProvider;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
@@ -75,6 +80,7 @@ import com.owncloud.android.lib.resources.status.NextcloudVersion;
 import com.owncloud.android.lib.resources.status.OwnCloudVersion;
 import com.owncloud.android.lib.resources.status.OwnCloudVersion;
 import com.owncloud.android.ui.activity.SyncedFoldersActivity;
 import com.owncloud.android.ui.activity.SyncedFoldersActivity;
 import com.owncloud.android.ui.notifications.NotificationUtils;
 import com.owncloud.android.ui.notifications.NotificationUtils;
+import com.owncloud.android.utils.appConfig.AppConfigManager;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.FilesSyncHelper;
 import com.owncloud.android.utils.FilesSyncHelper;
 import com.owncloud.android.utils.PermissionUtil;
 import com.owncloud.android.utils.PermissionUtil;
@@ -191,6 +197,8 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
     @SuppressWarnings("unused")
     @SuppressWarnings("unused")
     private boolean mBound;
     private boolean mBound;
 
 
+    private AppConfigManager appConfigManager;
+
     private static AppComponent appComponent;
     private static AppComponent appComponent;
 
 
     /**
     /**
@@ -281,6 +289,7 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
         return appComponent;
         return appComponent;
     }
     }
 
 
+
     @SuppressFBWarnings("ST")
     @SuppressFBWarnings("ST")
     @Override
     @Override
     public void onCreate() {
     public void onCreate() {
@@ -314,11 +323,15 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
 
 
         OwnCloudClientManagerFactory.setUserAgent(getUserAgent());
         OwnCloudClientManagerFactory.setUserAgent(getUserAgent());
 
 
-        try {
-            OwnCloudClientManagerFactory.setProxyHost(getResources().getString(R.string.proxy_host));
-            OwnCloudClientManagerFactory.setProxyPort(getResources().getInteger(R.integer.proxy_port));
-        } catch (Resources.NotFoundException e) {
-            // no proxy set
+        if (isClientBrandedPlus()) {
+            RestrictionsManager restrictionsManager = (RestrictionsManager) getSystemService(Context.RESTRICTIONS_SERVICE);
+            appConfigManager = new AppConfigManager(this, restrictionsManager.getApplicationRestrictions());
+            appConfigManager.setProxyConfig(isClientBrandedPlus());
+
+            // Listen app config changes
+            ContextExtensionsKt.registerBroadcastReceiver(this, restrictionsReceiver, restrictionsFilter, ReceiverFlag.NotExported);
+        } else {
+            setProxyForNonBrandedPlusClients();
         }
         }
 
 
         // initialise thumbnails cache on background thread
         // initialise thumbnails cache on background thread
@@ -364,9 +377,35 @@ public class MainApp extends MultiDexApplication implements HasAndroidInjector {
         } else if (event == Lifecycle.Event.ON_STOP) {
         } else if (event == Lifecycle.Event.ON_STOP) {
             passCodeManager.setCanAskPin(true);
             passCodeManager.setCanAskPin(true);
             Log_OC.d(TAG, "APP IN BACKGROUND");
             Log_OC.d(TAG, "APP IN BACKGROUND");
+        } else if (event == Lifecycle.Event.ON_RESUME) {
+            if (appConfigManager == null) return;
+            appConfigManager.setProxyConfig(isClientBrandedPlus());
+            Log_OC.d(TAG, "APP ON RESUME");
         }
         }
     });
     });
 
 
+    private void setProxyForNonBrandedPlusClients() {
+        try {
+            OwnCloudClientManagerFactory.setProxyHost(getResources().getString(R.string.proxy_host));
+            OwnCloudClientManagerFactory.setProxyPort(getResources().getInteger(R.integer.proxy_port));
+        } catch (Resources.NotFoundException e) {
+            Log_OC.d(TAG, "Error caught at setProxyForNonBrandedPlusClients: " + e);
+        }
+    }
+
+    public static boolean isClientBrandedPlus() {
+        return (getAppContext().getResources().getBoolean(R.bool.is_branded_plus_client));
+    }
+
+    private final IntentFilter restrictionsFilter = new IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED);
+
+    private final BroadcastReceiver restrictionsReceiver = new BroadcastReceiver() {
+        @Override public void onReceive(Context context, Intent intent) {
+            if (appConfigManager == null) return;
+            appConfigManager.setProxyConfig(isClientBrandedPlus());
+        }
+    };
+
     private void registerGlobalPassCodeProtection() {
     private void registerGlobalPassCodeProtection() {
         registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
         registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
 
 

+ 16 - 4
app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.java

@@ -21,6 +21,7 @@ import android.app.Activity;
 import android.content.ComponentName;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Context;
 import android.content.Intent;
 import android.content.Intent;
+import android.content.RestrictionsManager;
 import android.content.ServiceConnection;
 import android.content.ServiceConnection;
 import android.content.SharedPreferences;
 import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager;
@@ -83,6 +84,7 @@ import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
 import com.owncloud.android.lib.common.OwnCloudCredentials;
 import com.owncloud.android.lib.common.OwnCloudCredentials;
 import com.owncloud.android.lib.common.OwnCloudCredentialsFactory;
 import com.owncloud.android.lib.common.OwnCloudCredentialsFactory;
 import com.owncloud.android.lib.common.UserInfo;
 import com.owncloud.android.lib.common.UserInfo;
+import com.owncloud.android.lib.common.accounts.AccountUtils;
 import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
 import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
 import com.owncloud.android.lib.common.accounts.AccountUtils.Constants;
 import com.owncloud.android.lib.common.accounts.AccountUtils.Constants;
 import com.owncloud.android.lib.common.network.CertificateCombinedException;
 import com.owncloud.android.lib.common.network.CertificateCombinedException;
@@ -111,6 +113,7 @@ import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.ErrorMessageAdapter;
 import com.owncloud.android.utils.ErrorMessageAdapter;
 import com.owncloud.android.utils.PermissionUtil;
 import com.owncloud.android.utils.PermissionUtil;
 import com.owncloud.android.utils.WebViewUtil;
 import com.owncloud.android.utils.WebViewUtil;
+import com.owncloud.android.utils.appConfig.AppConfigManager;
 import com.owncloud.android.utils.theme.CapabilityUtils;
 import com.owncloud.android.utils.theme.CapabilityUtils;
 import com.owncloud.android.utils.theme.ViewThemeUtils;
 import com.owncloud.android.utils.theme.ViewThemeUtils;
 
 
@@ -318,9 +321,18 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
             mIsFirstAuthAttempt = savedInstanceState.getBoolean(KEY_AUTH_IS_FIRST_ATTEMPT_TAG);
             mIsFirstAuthAttempt = savedInstanceState.getBoolean(KEY_AUTH_IS_FIRST_ATTEMPT_TAG);
         }
         }
 
 
-        String webloginUrl = null;
         boolean webViewLoginMethod;
         boolean webViewLoginMethod;
-        if (getIntent().getBooleanExtra(EXTRA_USE_PROVIDER_AS_WEBLOGIN, false)) {
+        String webloginUrl = null;
+
+        if (MainApp.isClientBrandedPlus()) {
+            RestrictionsManager restrictionsManager = (RestrictionsManager) getSystemService(Context.RESTRICTIONS_SERVICE);
+            AppConfigManager appConfigManager = new AppConfigManager(this, restrictionsManager.getApplicationRestrictions());
+            webloginUrl = appConfigManager.getBaseUrl(MainApp.isClientBrandedPlus());
+        }
+
+        if (webloginUrl != null) {
+            webViewLoginMethod = true;
+        } else if (getIntent().getBooleanExtra(EXTRA_USE_PROVIDER_AS_WEBLOGIN, false)) {
             webViewLoginMethod = true;
             webViewLoginMethod = true;
             webloginUrl = getString(R.string.provider_registration_server);
             webloginUrl = getString(R.string.provider_registration_server);
         } else {
         } else {
@@ -1375,7 +1387,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
         // can be anything: email, name, name with whitespaces
         // can be anything: email, name, name with whitespaces
         String loginName = webViewUser;
         String loginName = webViewUser;
 
 
-        String accountName = com.owncloud.android.lib.common.accounts.AccountUtils.buildAccountName(uri, loginName);
+        String accountName = AccountUtils.buildAccountName(uri, loginName);
         Account newAccount = new Account(accountName, accountType);
         Account newAccount = new Account(accountName, accountType);
         if (accountManager.exists(newAccount)) {
         if (accountManager.exists(newAccount)) {
             // fail - not a new account, but an existing one; disallow
             // fail - not a new account, but an existing one; disallow
@@ -1480,7 +1492,7 @@ public class AuthenticatorActivity extends AccountAuthenticatorActivity
     public void onRequestPermissionsResult(int requestCode,
     public void onRequestPermissionsResult(int requestCode,
                                            @NonNull String[] permissions,
                                            @NonNull String[] permissions,
                                            @NonNull int[] grantResults) {
                                            @NonNull int[] grantResults) {
-        if (requestCode == PermissionUtil.PERMISSIONS_CAMERA) {// If request is cancelled, result arrays are empty.
+        if (requestCode == PERMISSIONS_CAMERA) {// If request is cancelled, result arrays are empty.
             if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
             if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                 // permission was granted
                 // permission was granted
                 startQRScanner();
                 startQRScanner();

+ 1 - 0
app/src/main/java/com/owncloud/android/ui/preview/PreviewMediaFragment.java

@@ -46,6 +46,7 @@ import com.nextcloud.client.network.ClientFactory;
 import com.nextcloud.common.NextcloudClient;
 import com.nextcloud.common.NextcloudClient;
 import com.nextcloud.ui.fileactions.FileActionsBottomSheet;
 import com.nextcloud.ui.fileactions.FileActionsBottomSheet;
 import com.nextcloud.utils.extensions.BundleExtensionsKt;
 import com.nextcloud.utils.extensions.BundleExtensionsKt;
+import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
 import com.owncloud.android.R;
 import com.owncloud.android.databinding.FragmentPreviewMediaBinding;
 import com.owncloud.android.databinding.FragmentPreviewMediaBinding;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.OCFile;

+ 1 - 0
app/src/main/java/com/owncloud/android/utils/DrawerMenuUtil.java

@@ -11,6 +11,7 @@ import android.content.res.Resources;
 import android.view.Menu;
 import android.view.Menu;
 
 
 import com.nextcloud.client.account.User;
 import com.nextcloud.client.account.User;
+import com.owncloud.android.MainApp;
 import com.owncloud.android.R;
 import com.owncloud.android.R;
 import com.owncloud.android.lib.resources.status.OCCapability;
 import com.owncloud.android.lib.resources.status.OCCapability;
 
 

+ 17 - 0
app/src/main/java/com/owncloud/android/utils/appConfig/AppConfigKeys.kt

@@ -0,0 +1,17 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.owncloud.android.utils.appConfig
+
+/**
+ * These keys are connected to app_config.xml
+ */
+enum class AppConfigKeys(val key: String) {
+    BaseUrl("base_url"),
+    ProxyHost("proxy_host"),
+    ProxyPort("proxy_port")
+}

+ 68 - 0
app/src/main/java/com/owncloud/android/utils/appConfig/AppConfigManager.kt

@@ -0,0 +1,68 @@
+/*
+ * Nextcloud - Android Client
+ *
+ * SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+package com.owncloud.android.utils.appConfig
+
+import android.content.Context
+import android.content.res.Resources
+import android.os.Bundle
+import android.text.TextUtils
+import com.owncloud.android.R
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
+import com.owncloud.android.lib.common.utils.Log_OC
+
+class AppConfigManager(private val context: Context, private val appRestrictions: Bundle) {
+
+    private val tag = "AppConfigManager"
+
+    fun setProxyConfig(isBrandedPlus: Boolean) {
+        if (!isBrandedPlus) {
+            Log_OC.d(tag, "Proxy configuration cannot be set. Client is not branded plus.")
+            return
+        }
+
+        val host = if (appRestrictions.containsKey(AppConfigKeys.ProxyHost.key)) {
+            appRestrictions.getString(AppConfigKeys.ProxyHost.key)
+        } else {
+            context.getString(R.string.proxy_host)
+        }
+
+        val port = if (appRestrictions.containsKey(AppConfigKeys.ProxyPort.key)) {
+            appRestrictions.getInt(AppConfigKeys.ProxyPort.key)
+        } else {
+            context.resources.getInteger(R.integer.proxy_port)
+        }
+
+        if (TextUtils.isEmpty(host) || port == -1) {
+            Log_OC.d(tag, "Proxy configuration cannot be found")
+            return
+        }
+
+        try {
+            OwnCloudClientManagerFactory.setProxyHost(host)
+            OwnCloudClientManagerFactory.setProxyPort(port)
+
+            Log_OC.d(tag, "Proxy configuration successfully set")
+        } catch (e: Resources.NotFoundException) {
+            Log_OC.e(tag, "Proxy config cannot able to set due to: $e")
+        }
+    }
+
+    fun getBaseUrl(isBrandedPlus: Boolean): String? {
+        if (!isBrandedPlus) {
+            Log_OC.d(tag, "Proxy configuration cannot be set. Client is not branded plus. Default url applied")
+            return null
+        }
+
+        return if (appRestrictions.containsKey(AppConfigKeys.BaseUrl.key)) {
+            appRestrictions.getString(AppConfigKeys.BaseUrl.key)
+        } else {
+            Log_OC.d(tag, "BaseUrl configuration cannot be found, default url applied")
+            null
+        }
+    }
+}

+ 1 - 0
app/src/main/res/values/setup.xml

@@ -43,6 +43,7 @@
     <bool name="show_external_links">true</bool>
     <bool name="show_external_links">true</bool>
     <bool name="show_outdated_server_warning">true</bool>
     <bool name="show_outdated_server_warning">true</bool>
     <bool name="is_branded_client">false</bool>
     <bool name="is_branded_client">false</bool>
+    <bool name="is_branded_plus_client">false</bool>
 
 
     <!-- Calendar & Contacts backup -->
     <!-- Calendar & Contacts backup -->
     <string name="contacts_backup_folder">/.Contacts-Backup</string>
     <string name="contacts_backup_folder">/.Contacts-Backup</string>

+ 4 - 0
app/src/main/res/values/strings.xml

@@ -26,6 +26,10 @@
 
 
     <string name="ecosystem_apps_display_assistant">Assistant</string>
     <string name="ecosystem_apps_display_assistant">Assistant</string>
 
 
+    <string name="app_config_base_url_title">Base Url</string>
+    <string name="app_config_proxy_host_title">Proxy Host Name</string>
+    <string name="app_config_proxy_port_title">Proxy Port</string>
+
     <string name="assistant_screen_task_types_error_state_message">Unable to fetch task types, please check your internet connection.</string>
     <string name="assistant_screen_task_types_error_state_message">Unable to fetch task types, please check your internet connection.</string>
     <string name="assistant_screen_task_list_error_state_message">Unable to fetch task list, please check your internet connection.</string>
     <string name="assistant_screen_task_list_error_state_message">Unable to fetch task list, please check your internet connection.</string>
 
 

+ 28 - 0
app/src/main/res/xml/app_config.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Nextcloud - Android Client
+  ~
+  ~ SPDX-FileCopyrightText: 2024 Your Name <your@email.com>
+  ~ SPDX-License-Identifier: AGPL-3.0-or-later
+  -->
+
+<restrictions xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <restriction
+        android:key="proxy_host"
+        android:defaultValue=""
+        android:restrictionType="string"
+        android:title="@string/app_config_proxy_host_title" />
+
+    <restriction
+        android:key="proxy_port"
+        android:defaultValue="-1"
+        android:restrictionType="integer"
+        android:title="@string/app_config_proxy_port_title" />
+
+    <restriction
+        android:key="base_url"
+        android:defaultValue=""
+        android:restrictionType="string"
+        android:title="@string/app_config_base_url_title" />
+
+</restrictions>