diff options
Diffstat (limited to 'app/src/main')
101 files changed, 4041 insertions, 913 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cbc06854..091fa0b6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,34 +1,19 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2011 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="se.leap.bitmaskclient"> <!-- package is overwritten in build.gradle --> - <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> + <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.VIBRATE" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" - tools:ignore="ScopedStorage" /> - <!-- Used to show all apps in the allowed Apps selection --> + <uses-permission + android:name="android.permission.WRITE_EXTERNAL_STORAGE" + tools:ignore="ScopedStorage" /> <!-- Used to show all apps in the allowed Apps selection --> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <application @@ -42,6 +27,11 @@ android:appCategory="productivity" android:logo="@mipmap/ic_launcher" android:theme="@style/BitmaskTheme"> + <activity + android:name=".providersetup.activities.SetupActivity" + android:launchMode="singleInstance" + android:exported="false" /> + <service android:name="de.blinkt.openvpn.core.OpenVPNService" android:exported="false" @@ -67,11 +57,11 @@ <receiver android:name=".base.OnBootReceiver" android:enabled="true" - android:permission="android.permission.RECEIVE_BOOT_COMPLETED" - android:exported="true"> - <intent-filter android:priority="999"> - <action android:name="android.intent.action.BOOT_COMPLETED" /> - </intent-filter> + android:exported="true" + android:permission="android.permission.RECEIVE_BOOT_COMPLETED"> + <intent-filter android:priority="999"> + <action android:name="android.intent.action.BOOT_COMPLETED" /> + </intent-filter> </receiver> <activity @@ -82,11 +72,11 @@ android:theme="@style/invisibleTheme" /> <activity android:name=".base.StartActivity" + android:exported="true" android:label="@string/app_name" android:launchMode="singleTop" android:taskAffinity="" android:theme="@style/SplashTheme" - android:exported="true" > <intent-filter android:label="@string/app_name"> @@ -97,29 +87,23 @@ <action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" /> </intent-filter> </activity> - <activity android:name=".base.MainActivity" android:label="@string/app_name" android:launchMode="singleTop" /> - <activity android:name=".providersetup.ProviderListActivity" android:label="@string/configuration_wizard_title" /> - <activity android:name=".providersetup.activities.CustomProviderSetupActivity" android:label="@string/setup_provider" /> - <activity android:name=".providersetup.AddProviderActivity" android:label="@string/add_provider" /> - <activity android:name=".providersetup.ProviderDetailActivity" android:label="@string/provider_details_title" android:launchMode="singleTop" /> - <activity android:name=".providersetup.activities.LoginActivity" /> <activity android:name=".providersetup.activities.SignupActivity" /> @@ -144,7 +128,6 @@ android:name="android.service.quicksettings.ACTIVE_TILE" android:value="false" /> </service> - </application> -</manifest> +</manifest>
\ No newline at end of file diff --git a/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java b/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java index 219c1394..c3083a1f 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java +++ b/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java @@ -5,6 +5,8 @@ package de.blinkt.openvpn.core; +import static de.blinkt.openvpn.core.OpenVPNManagement.pauseReason; + import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -14,19 +16,14 @@ import android.net.NetworkInfo; import android.net.NetworkInfo.State; import android.os.Handler; import android.os.Looper; -import android.preference.PreferenceManager; -import se.leap.bitmaskclient.R; +import java.util.LinkedList; + import de.blinkt.openvpn.core.VpnStatus.ByteCountListener; +import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.utils.PreferenceHelper; import se.leap.bitmaskclient.tethering.TetheringObservable; -import java.util.LinkedList; -import java.util.Objects; -import java.util.StringTokenizer; - -import static de.blinkt.openvpn.core.OpenVPNManagement.pauseReason; - public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountListener, OpenVPNManagement.PausedStateCallback { private final Handler mDisconnectHandler; private int lastNetwork = -1; @@ -143,7 +140,7 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { networkStateChange(context); } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { - boolean screenOffPause = PreferenceHelper.getSaveBattery(context); + boolean screenOffPause = PreferenceHelper.getSaveBattery(); boolean isTethering = TetheringObservable.getInstance().getTetheringState().isVpnTetheringRunning(); if (screenOffPause && !isTethering) { if (VpnStatus.getLastConnectedVpnProfile() != null && !VpnStatus.getLastConnectedVpnProfile().mPersistTun) diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java index 6adffda1..575f1f59 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java +++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java @@ -335,7 +335,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac Log.d(TAG, "Starting VPN due to isAlwaysOn system settings or app crash."); startWithForegroundNotification(); - mProfile = VpnStatus.getLastConnectedVpnProfile(this); + mProfile = VpnStatus.getLastConnectedVpnProfile(); VpnStatus.logInfo(R.string.service_restarted); if (mProfile != null) { @@ -357,7 +357,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac /* start the OpenVPN process itself in a background thread */ new Thread(this::startOpenVPN).start(); - VpnStatus.setLastConnectedVpnProfile(getApplicationContext(), mProfile); + VpnStatus.setLastConnectedVpnProfile(mProfile); return START_STICKY; } diff --git a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java index 8115548f..ecc03a19 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java +++ b/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java @@ -153,19 +153,14 @@ public class VpnStatus { } public static VpnProfile getLastConnectedVpnProfile() { - return lastConnectedProfile; + return lastConnectedProfile != null ? lastConnectedProfile : PreferenceHelper.getLastConnectedVpnProfile(); } - public static VpnProfile getLastConnectedVpnProfile(Context context) { - return PreferenceHelper.getLastConnectedVpnProfile(context); - } - - /** * Sets the profile that is connected (to connect if the service restarts) */ - public static void setLastConnectedVpnProfile(Context context, VpnProfile connectedProfile) { - PreferenceHelper.setLastUsedVpnProfile(context, connectedProfile); + public static void setLastConnectedVpnProfile(VpnProfile connectedProfile) { + PreferenceHelper.setLastUsedVpnProfile(connectedProfile); lastConnectedProfile = connectedProfile; setConnectedVPNProfile(lastConnectedProfile.getUUIDString()); } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java b/app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java index 74b789a9..a66f75d7 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/BitmaskApp.java @@ -56,6 +56,8 @@ public class BitmaskApp extends MultiDexApplication { private DownloadBroadcastReceiver downloadBroadcastReceiver; private TorStatusObservable torStatusObservable; + private PreferenceHelper preferenceHelper; + @Override public void onCreate() { @@ -63,11 +65,11 @@ public class BitmaskApp extends MultiDexApplication { // Normal app init code...*/ PRNGFixes.apply(); Security.insertProviderAt(Conscrypt.newProvider(), 1); - SharedPreferences preferences = PreferenceHelper.getSharedPreferences(this); + preferenceHelper = new PreferenceHelper(this); providerObservable = ProviderObservable.getInstance(); - providerObservable.updateProvider(getSavedProviderFromSharedPreferences(preferences)); + providerObservable.updateProvider(getSavedProviderFromSharedPreferences()); torStatusObservable = TorStatusObservable.getInstance(); - EipSetupObserver.init(this, preferences); + EipSetupObserver.init(this); AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); if (!isCalyxOSWithTetheringSupport(this)) { TetheringStateManager.getInstance().init(this); diff --git a/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java index 248d96c7..b459f75d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java @@ -33,7 +33,6 @@ import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_CONFIGURE_LEAP; import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_LOG_IN; import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_SWITCH_PROVIDER; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.storeProviderInPreferences; import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_INVALID_VPN_CERTIFICATE; import static se.leap.bitmaskclient.eip.EIP.EIPErrors.ERROR_VPN_PREPARE; import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORID; @@ -47,7 +46,6 @@ import static se.leap.bitmaskclient.providersetup.ProviderAPI.USER_MESSAGE; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.os.Bundle; import android.util.Log; import android.view.Gravity; @@ -92,7 +90,6 @@ public class MainActivity extends AppCompatActivity implements EipSetupListener, public final static String TAG = MainActivity.class.getSimpleName(); private Provider provider; - private SharedPreferences preferences; private NavigationDrawerFragment navigationDrawerFragment; public final static String ACTION_SHOW_VPN_FRAGMENT = "action_show_vpn_fragment"; @@ -112,7 +109,6 @@ public class MainActivity extends AppCompatActivity implements EipSetupListener, navigationDrawerFragment = (NavigationDrawerFragment) getSupportFragmentManager().findFragmentById(R.id.navigation_drawer); - preferences = PreferenceHelper.getSharedPreferences(this); provider = ProviderObservable.getInstance().getCurrentProvider(); EipSetupObserver.addListener(this); @@ -253,10 +249,10 @@ public class MainActivity extends AppCompatActivity implements EipSetupListener, return; } - storeProviderInPreferences(preferences, provider); + PreferenceHelper.storeProviderInPreferences(provider); ProviderObservable.getInstance().updateProvider(provider); if (!provider.supportsPluggableTransports()) { - PreferenceHelper.useBridges(this, false); + PreferenceHelper.useBridges(false); } navigationDrawerFragment.refresh(); diff --git a/app/src/main/java/se/leap/bitmaskclient/base/OnBootReceiver.java b/app/src/main/java/se/leap/bitmaskclient/base/OnBootReceiver.java index a508329c..876f71da 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/OnBootReceiver.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/OnBootReceiver.java @@ -3,12 +3,10 @@ package se.leap.bitmaskclient.base; import static android.content.Intent.ACTION_BOOT_COMPLETED; import static se.leap.bitmaskclient.base.models.Constants.APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE; import static se.leap.bitmaskclient.base.models.Constants.EIP_RESTART_ON_BOOT; -import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.os.Build; import android.util.Log; @@ -17,8 +15,6 @@ import se.leap.bitmaskclient.base.utils.PreferenceHelper; public class OnBootReceiver extends BroadcastReceiver { - SharedPreferences preferences; - // Debug: am broadcast -a android.intent.action.BOOT_COMPLETED @Override public void onReceive(Context context, Intent intent) { @@ -26,9 +22,8 @@ public class OnBootReceiver extends BroadcastReceiver { if (intent == null || !ACTION_BOOT_COMPLETED.equals(intent.getAction())) { return; } - preferences = PreferenceHelper.getSharedPreferences(context); - boolean providerConfigured = !preferences.getString(PROVIDER_VPN_CERTIFICATE, "").isEmpty(); - boolean startOnBoot = preferences.getBoolean(EIP_RESTART_ON_BOOT, false) && Build.VERSION.SDK_INT < Build.VERSION_CODES.O; + boolean providerConfigured = !PreferenceHelper.getProviderVPNCertificate().isEmpty(); + boolean startOnBoot = PreferenceHelper.getRestartOnBoot() && Build.VERSION.SDK_INT < Build.VERSION_CODES.O; boolean isAlwaysOnConfigured = VpnStatus.isAlwaysOn(); Log.d("OpenVPN", "OpenVPN onBoot intent received. Provider configured? " + providerConfigured + " Start on boot? " + startOnBoot + " isAlwaysOn feature configured: " + isAlwaysOnConfigured); if (providerConfigured) { diff --git a/app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java b/app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java index 94000a0f..715367f5 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java @@ -30,7 +30,6 @@ import static se.leap.bitmaskclient.base.utils.PreferenceHelper.storeProviderInP import android.app.Activity; import android.content.Intent; -import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.os.Bundle; import android.util.Log; @@ -54,8 +53,8 @@ import se.leap.bitmaskclient.base.models.ProviderObservable; import se.leap.bitmaskclient.base.utils.DateHelper; import se.leap.bitmaskclient.base.utils.PreferenceHelper; import se.leap.bitmaskclient.eip.EipCommand; -import se.leap.bitmaskclient.providersetup.ProviderListActivity; import se.leap.bitmaskclient.providersetup.activities.CustomProviderSetupActivity; +import se.leap.bitmaskclient.providersetup.activities.SetupActivity; /** * Activity shown at startup. Evaluates if App is started for the first time or has been upgraded @@ -75,12 +74,10 @@ public class StartActivity extends Activity{ private int versionCode; private int previousVersionCode; - private SharedPreferences preferences; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - preferences = PreferenceHelper.getSharedPreferences(this); Log.d(TAG, "Started"); @@ -131,7 +128,7 @@ public class StartActivity extends Activity{ private int checkAppStart() { try { versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode; - previousVersionCode = preferences.getInt(PREFERENCES_APP_VERSION, -1); + previousVersionCode = PreferenceHelper.getAppVersion(); // versions do match -> normal start if (versionCode == previousVersionCode) { @@ -163,10 +160,9 @@ public class StartActivity extends Activity{ */ private void executeUpgrade() { if (hasNewFeature(FeatureVersionCode.RENAMED_EIP_IN_PREFERENCES)) { - String eipJson = preferences.getString(PROVIDER_KEY, null); + String eipJson = PreferenceHelper.getString(PROVIDER_KEY, null); if (eipJson != null) { - preferences.edit().putString(PROVIDER_EIP_DEFINITION, eipJson). - remove(PROVIDER_KEY).apply(); + PreferenceHelper.putString(PROVIDER_EIP_DEFINITION, eipJson); } } @@ -176,7 +172,7 @@ public class StartActivity extends Activity{ // next setup Provider provider = ProviderObservable.getInstance().getCurrentProvider(); if (provider != null && !provider.isDefault()) { - PreferenceHelper.deleteProviderDetailsFromPreferences(preferences, provider.getDomain()); + PreferenceHelper.deleteProviderDetailsFromPreferences(provider.getDomain()); ProviderObservable.getInstance().updateProvider(new Provider()); } } @@ -190,20 +186,19 @@ public class StartActivity extends Activity{ // deletion of current configured provider so that a new provider setup is triggered Provider provider = ProviderObservable.getInstance().getCurrentProvider(); if (provider != null && !provider.isDefault()) { - PreferenceHelper.deleteProviderDetailsFromPreferences(preferences, provider.getDomain()); - PreferenceHelper.deleteCurrentProviderDetailsFromPreferences(preferences); + PreferenceHelper.deleteProviderDetailsFromPreferences(provider.getDomain()); + PreferenceHelper.deleteCurrentProviderDetailsFromPreferences(); ProviderObservable.getInstance().updateProvider(new Provider()); } } if (hasNewFeature(FeatureVersionCode.ENCRYPTED_SHARED_PREFS)) { PreferenceHelper.migrateToEncryptedPrefs(this); - preferences = PreferenceHelper.getSharedPreferences(this); } // always check if manual gateway selection feature switch has been disabled - if (!BuildConfig.allow_manual_gateway_selection && PreferenceHelper.getPreferredCity(this) != null) { - PreferenceHelper.setPreferredCity(this, null); + if (!BuildConfig.allow_manual_gateway_selection && PreferenceHelper.getPreferredCity() != null) { + PreferenceHelper.setPreferredCity(null); } // ensure all upgrades have passed before storing new information @@ -220,7 +215,7 @@ public class StartActivity extends Activity{ } private void storeAppVersion() { - preferences.edit().putInt(PREFERENCES_APP_VERSION, versionCode).apply(); + PreferenceHelper.setAppVersion(versionCode); } private void prepareEIP() { @@ -230,9 +225,9 @@ public class StartActivity extends Activity{ if (getIntent() != null && getIntent().getBooleanExtra(EIP_RESTART_ON_BOOT, false)) { EipCommand.startVPN(this, true); finish(); - } else if (PreferenceHelper.getRestartOnUpdate(this.getApplicationContext())) { + } else if (PreferenceHelper.getRestartOnUpdate()) { // This is relevant for web build flavor apks - PreferenceHelper.restartOnUpdate(this.getApplicationContext(), false); + PreferenceHelper.restartOnUpdate(false); EipCommand.startVPN(this, false); showNextActivity(provider); finish(); @@ -249,7 +244,7 @@ public class StartActivity extends Activity{ getIntent().removeExtra(APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE); } if (isDefaultBitmask()) { - startActivityForResult(new Intent(this, ProviderListActivity.class), REQUEST_CODE_CONFIGURE_LEAP); + startActivityForResult(new Intent(this, SetupActivity.class), REQUEST_CODE_CONFIGURE_LEAP); } else { // custom branded app startActivityForResult(new Intent(this, CustomProviderSetupActivity.class), REQUEST_CODE_CONFIGURE_LEAP); } @@ -261,7 +256,7 @@ public class StartActivity extends Activity{ if (requestCode == REQUEST_CODE_CONFIGURE_LEAP) { if (resultCode == RESULT_OK && data != null && data.hasExtra(Provider.KEY)) { Provider provider = data.getParcelableExtra(Provider.KEY); - storeProviderInPreferences(preferences, provider); + storeProviderInPreferences( provider); ProviderObservable.getInstance().updateProvider(provider); EipCommand.startVPN(this, false); showNextActivity(provider); @@ -298,7 +293,7 @@ public class StartActivity extends Activity{ lastSeenHashes.add(hash); p.setMotdLastSeenHashes(lastSeenHashes); p.setLastMotdSeen(System.currentTimeMillis()); - PreferenceHelper.persistProviderAsync(this, p); + PreferenceHelper.persistProviderAsync(p); ProviderObservable.getInstance().updateProvider(p); } showMotdFragment(message); diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/AlwaysOnDialog.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/AlwaysOnDialog.java index 7d457406..e68ba170 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/AlwaysOnDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/AlwaysOnDialog.java @@ -68,7 +68,7 @@ public class AlwaysOnDialog extends AppCompatDialogFragment { builder.setView(view) .setPositiveButton(android.R.string.ok, (dialog, id) -> { if (doNotShowAgainCheckBox.isChecked()) { - saveShowAlwaysOnDialog(getContext(), false); + saveShowAlwaysOnDialog(false); } Intent intent = new Intent("android.net.vpn.SETTINGS"); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/DonationReminderDialog.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/DonationReminderDialog.java index c39386fc..08346791 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/DonationReminderDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/DonationReminderDialog.java @@ -67,13 +67,11 @@ public class DonationReminderDialog extends AppCompatDialogFragment { } catch (ActivityNotFoundException e) { e.printStackTrace(); } - PreferenceHelper.putString(getContext(), LAST_DONATION_REMINDER_DATE, - DateHelper.getCurrentDateString()); + PreferenceHelper.lastDonationReminderDate(DateHelper.getCurrentDateString()); dismiss(); }); btnLater.setOnClickListener(v -> { - PreferenceHelper.putString(getContext(), LAST_DONATION_REMINDER_DATE, - DateHelper.getCurrentDateString()); + PreferenceHelper.lastDonationReminderDate(DateHelper.getCurrentDateString()); dismiss(); }); @@ -100,9 +98,9 @@ public class DonationReminderDialog extends AppCompatDialogFragment { return false; } - String firstTimeUserDate = PreferenceHelper.getString(context, FIRST_TIME_USER_DATE, null); + String firstTimeUserDate = PreferenceHelper.getFirstTimeUserDate(); if (firstTimeUserDate == null) { - PreferenceHelper.putString(context, FIRST_TIME_USER_DATE, DateHelper.getCurrentDateString()); + PreferenceHelper.firstTimeUserDate(DateHelper.getCurrentDateString()); return false; } @@ -114,7 +112,7 @@ public class DonationReminderDialog extends AppCompatDialogFragment { return false; } - String lastDonationReminderDate = PreferenceHelper.getString(context, LAST_DONATION_REMINDER_DATE, null); + String lastDonationReminderDate = PreferenceHelper.getLastDonationReminderDate(); if (lastDonationReminderDate == null) { return true; } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java index a8d11869..f50b69a5 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/EipFragment.java @@ -20,7 +20,6 @@ import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message; import static se.leap.bitmaskclient.base.models.Constants.ASK_TO_CANCEL_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.base.models.Constants.EIP_EARLY_ROUTES; -import static se.leap.bitmaskclient.base.models.Constants.EIP_RESTART_ON_BOOT; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_CONFIGURE_LEAP; import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_LOG_IN; @@ -36,7 +35,6 @@ import static se.leap.bitmaskclient.providersetup.ProviderAPI.USER_MESSAGE; import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.content.SharedPreferences; import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -93,8 +91,6 @@ public class EipFragment extends Fragment implements Observer { public final static String TAG = EipFragment.class.getSimpleName(); - - private SharedPreferences preferences; private Provider provider; AppCompatImageView background; @@ -156,12 +152,6 @@ public class EipFragment extends Fragment implements Observer { eipStatus = EipStatus.getInstance(); providerObservable = ProviderObservable.getInstance(); torStatusObservable = TorStatusObservable.getInstance(); - Activity activity = getActivity(); - if (activity != null) { - preferences = PreferenceHelper.getSharedPreferences(activity); - } else { - Log.e(TAG, "activity is null in onCreate - no preferences set!"); - } gatewaysManager = new GatewaysManager(getContext()); } @@ -274,7 +264,7 @@ public class EipFragment extends Fragment implements Observer { } private void saveStatus(boolean restartOnBoot) { - preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, restartOnBoot).apply(); + PreferenceHelper.restartOnBoot(restartOnBoot); } void handleIcon() { @@ -438,7 +428,7 @@ public class EipFragment extends Fragment implements Observer { Log.d(TAG, "eip fragment eipStatus state: " + eipStatus.getState() + " - level: " + eipStatus.getLevel() + " - is reconnecting: " + eipStatus.isReconnecting()); if (eipStatus.isUpdatingVpnCert()) { setMainButtonEnabled(true); - String city = getPreferredCity(getContext()); + String city = getPreferredCity(); String locationName = VpnStatus.getCurrentlyConnectingVpnName() != null ? VpnStatus.getCurrentlyConnectingVpnName() : city == null ? getString(R.string.gateway_selection_recommended_location) : city; @@ -461,7 +451,7 @@ public class EipFragment extends Fragment implements Observer { setActivityBarColor(R.color.bg_connecting_top, R.color.bg_connecting_top_light_transparent); } else if (eipStatus.isConnecting()) { setMainButtonEnabled(true); - String city = getPreferredCity(getContext()); + String city = getPreferredCity(); String locationName = VpnStatus.getCurrentlyConnectingVpnName() != null ? VpnStatus.getCurrentlyConnectingVpnName() : city == null ? getString(R.string.gateway_selection_recommended_location) : city; @@ -478,11 +468,11 @@ public class EipFragment extends Fragment implements Observer { } else if (eipStatus.isConnected()) { setMainButtonEnabled(true); mainButton.updateState(true, false); - Connection.TransportType transportType = PreferenceHelper.getUseBridges(getContext()) ? Connection.TransportType.OBFS4 : Connection.TransportType.OPENVPN; - locationButton.setLocationLoad(PreferenceHelper.useObfuscationPinning(getContext()) ? GatewaysManager.Load.UNKNOWN : gatewaysManager.getLoadForLocation(VpnStatus.getLastConnectedVpnName(), transportType)); + Connection.TransportType transportType = PreferenceHelper.getUseBridges() ? Connection.TransportType.OBFS4 : Connection.TransportType.OPENVPN; + locationButton.setLocationLoad(PreferenceHelper.useObfuscationPinning() ? GatewaysManager.Load.UNKNOWN : gatewaysManager.getLoadForLocation(VpnStatus.getLastConnectedVpnName(), transportType)); locationButton.setText(VpnStatus.getLastConnectedVpnName()); locationButton.showBridgeIndicator(VpnStatus.isUsingBridges()); - locationButton.showRecommendedIndicator(getPreferredCity(getContext()) == null); + locationButton.showRecommendedIndicator(getPreferredCity() == null); mainDescription.setText(R.string.eip_status_secured); subDescription.setText(null); background.setImageResource(R.drawable.bg_connected); @@ -495,7 +485,7 @@ public class EipFragment extends Fragment implements Observer { locationButton.setText(VpnStatus.getCurrentlyConnectingVpnName()); locationButton.showBridgeIndicator(VpnStatus.isUsingBridges()); locationButton.showBridgeIndicator(VpnStatus.isUsingBridges()); - locationButton.showRecommendedIndicator(getPreferredCity(getContext())== null); + locationButton.showRecommendedIndicator(getPreferredCity()== null); mainDescription.setText(R.string.eip_state_connected); subDescription.setText(R.string.eip_state_no_network); background.setImageResource(R.drawable.bg_connecting); @@ -542,7 +532,7 @@ public class EipFragment extends Fragment implements Observer { mainButton.updateState(false, false); locationButton.setLocationLoad(UNKNOWN); locationButton.showBridgeIndicator(false); - String city = getPreferredCity(getContext()); + String city = getPreferredCity(); locationButton.setText(city == null ? getString(R.string.gateway_selection_recommended_location) : city); locationButton.showRecommendedIndicator(false); mainDescription.setText(R.string.eip_status_unsecured); diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/ExcludeAppsFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/ExcludeAppsFragment.java index 98c2e438..89b167f3 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/ExcludeAppsFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/ExcludeAppsFragment.java @@ -255,7 +255,7 @@ public class ExcludeAppsFragment extends Fragment implements AdapterView.OnItemC @Override public void onDestroy() { - PreferenceHelper.setExcludedApps(this.getActivity().getApplicationContext(), apps); + PreferenceHelper.setExcludedApps(apps); super.onDestroy(); } @@ -263,7 +263,7 @@ public class ExcludeAppsFragment extends Fragment implements AdapterView.OnItemC public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - apps = PreferenceHelper.getExcludedApps(this.getContext()); + apps = PreferenceHelper.getExcludedApps(); setHasOptionsMenu(true); } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java index 793c6407..dc7f64e1 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/GatewaySelectionFragment.java @@ -73,7 +73,6 @@ public class GatewaySelectionFragment extends Fragment implements Observer, Loca private SelectLocationEntry recommendedLocation; private GatewaysManager gatewaysManager; private EipStatus eipStatus; - private SharedPreferences preferences; private Connection.TransportType selectedTransport; private AppCompatTextView bridgesHint; private AppCompatTextView disableBridges; @@ -87,15 +86,14 @@ public class GatewaySelectionFragment extends Fragment implements Observer, Loca super.onCreate(savedInstanceState); gatewaysManager = new GatewaysManager(getContext()); eipStatus = EipStatus.getInstance(); - preferences = PreferenceHelper.getSharedPreferences(getContext()); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment - selectedTransport = getUseBridges(preferences) ? PT : OPENVPN; - preferences.registerOnSharedPreferenceChangeListener(this); + selectedTransport = getUseBridges() ? PT : OPENVPN; + PreferenceHelper.registerOnSharedPreferenceChangeListener(this); eipStatus.addObserver(this); return inflater.inflate(R.layout.f_gateway_selection, container, false); } @@ -113,7 +111,7 @@ public class GatewaySelectionFragment extends Fragment implements Observer, Loca public void onDestroyView() { super.onDestroyView(); eipStatus.deleteObserver(this); - preferences.unregisterOnSharedPreferenceChangeListener(this); + PreferenceHelper.unregisterOnSharedPreferenceChangeListener(this); } private void initRecyclerView() { @@ -140,17 +138,17 @@ public class GatewaySelectionFragment extends Fragment implements Observer, Loca private void initBridgesHint(@NonNull View view) { bridgesHint = view.findViewById(R.id.manual_subtitle); - bridgesHint.setVisibility(getUseBridges(getContext()) ? VISIBLE : GONE); + bridgesHint.setVisibility(getUseBridges() ? VISIBLE : GONE); disableBridges = view.findViewById(R.id.disable_bridges); - disableBridges.setVisibility(getUseBridges(getContext()) ? VISIBLE : GONE); + disableBridges.setVisibility(getUseBridges() ? VISIBLE : GONE); disableBridges.setOnClickListener(v -> { - useBridges(getContext(), false); + useBridges(false); }); } private void updateRecommendedLocation() { Location location = new Location(); - boolean isManualSelection = PreferenceHelper.getPreferredCity(getContext()) != null; + boolean isManualSelection = PreferenceHelper.getPreferredCity() != null; if (!isManualSelection && eipStatus.isConnected()) { try { location = gatewaysManager.getLocation(VpnStatus.getCurrentlyConnectingVpnName()).clone(); @@ -171,7 +169,7 @@ public class GatewaySelectionFragment extends Fragment implements Observer, Loca if (context == null) { return; } - PreferenceHelper.setPreferredCity(context, preferredCity); + PreferenceHelper.setPreferredCity(preferredCity); EipCommand.startVPN(context, false); try { Intent intent = new Intent(context, MainActivity.class); @@ -207,7 +205,7 @@ public class GatewaySelectionFragment extends Fragment implements Observer, Loca @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (key.equals(USE_BRIDGES)) { - boolean showBridges = getUseBridges(sharedPreferences); + boolean showBridges = getUseBridges(); selectedTransport = showBridges ? PT : OPENVPN; gatewaysManager.updateTransport(selectedTransport); locationListAdapter.updateTransport(selectedTransport, gatewaysManager); diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/LogFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/LogFragment.java index 8f73595d..c7dd25da 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/LogFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/LogFragment.java @@ -22,7 +22,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.Handler.Callback; import android.os.Message; -import android.preference.PreferenceManager; import android.text.SpannableString; import android.text.format.DateFormat; import android.view.LayoutInflater; @@ -56,12 +55,11 @@ import de.blinkt.openvpn.core.ConnectionStatus; import de.blinkt.openvpn.core.LogItem; import de.blinkt.openvpn.core.OpenVPNManagement; import de.blinkt.openvpn.core.OpenVPNService; -import de.blinkt.openvpn.core.Preferences; import de.blinkt.openvpn.core.VpnStatus; import de.blinkt.openvpn.core.VpnStatus.LogListener; import de.blinkt.openvpn.core.VpnStatus.StateListener; import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.base.models.Constants; +import se.leap.bitmaskclient.base.utils.PreferenceHelper; public class LogFragment extends ListFragment implements StateListener, SeekBar.OnSeekBarChangeListener, RadioGroup.OnCheckedChangeListener, VpnStatus.ByteCountListener { public static final String TAG = LogFragment.class.getSimpleName(); @@ -511,9 +509,9 @@ public class LogFragment extends ListFragment implements StateListener, SeekBar. } mClearLogCheckBox = v.findViewById(R.id.clearlogconnect); - mClearLogCheckBox.setChecked(PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(Constants.CLEARLOG, true)); + mClearLogCheckBox.setChecked(PreferenceHelper.getClearLog()); mClearLogCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> - Preferences.getDefaultSharedPreferences(getActivity()).edit().putBoolean(Constants.CLEARLOG, isChecked).apply()); + PreferenceHelper.setClearLog(isChecked)); mSpeedView = v.findViewById(R.id.speed); diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java index ae25c61c..3dbdbe64 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/MainActivityErrorDialog.java @@ -134,22 +134,22 @@ public class MainActivityErrorDialog extends DialogFragment { stopVoidVpnIntent.setAction(EIP_ACTION_STOP_BLOCKING_VPN); getContext().startService(stopVoidVpnIntent); }); - if (getPreferredCity(applicationContext) != null) { + if (getPreferredCity() != null) { builder.setPositiveButton(R.string.warning_option_try_best, (dialog, which) -> { new Thread(() -> { - setPreferredCity(applicationContext, null); + setPreferredCity(null); EipCommand.startVPN(applicationContext, false); }).start(); }); } else if (provider.supportsPluggableTransports()) { - if (getUseBridges(applicationContext)) { + if (getUseBridges()) { builder.setPositiveButton(warning_option_try_ovpn, ((dialog, which) -> { - useBridges(applicationContext, false); + useBridges(false); EipCommand.startVPN(applicationContext, false); })); } else { builder.setPositiveButton(warning_option_try_pt, ((dialog, which) -> { - useBridges(applicationContext, true); + useBridges(true); EipCommand.startVPN(applicationContext, false); })); } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java index f51fd342..18f20a6f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java @@ -17,7 +17,6 @@ package se.leap.bitmaskclient.base.fragments; -import static android.content.Context.MODE_PRIVATE; import static android.view.View.GONE; import static android.view.View.VISIBLE; import static se.leap.bitmaskclient.base.models.Constants.DONATION_URL; @@ -31,6 +30,7 @@ import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getSaveBattery; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.saveBattery; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; @@ -38,8 +38,6 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; @@ -67,6 +65,7 @@ import se.leap.bitmaskclient.base.views.IconSwitchEntry; import se.leap.bitmaskclient.base.views.IconTextEntry; import se.leap.bitmaskclient.eip.EipStatus; import se.leap.bitmaskclient.providersetup.ProviderListActivity; +import se.leap.bitmaskclient.providersetup.activities.SetupActivity; import se.leap.bitmaskclient.tethering.TetheringObservable; /** @@ -99,8 +98,6 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen private volatile boolean wasPaused; private volatile boolean shouldCloseOnResume; - private SharedPreferences preferences; - private final static String KEY_SHOW_SAVE_BATTERY_ALERT = "KEY_SHOW_SAVE_BATTERY_ALERT"; private volatile boolean showSaveBattery = false; AlertDialog alertDialog; @@ -108,10 +105,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - // Reads in the flag indicating whether or not the user has demonstrated awareness of the - // drawer. See PREF_USER_LEARNED_DRAWER for details. - preferences = PreferenceHelper.getSharedPreferences(getContext()); - preferences.registerOnSharedPreferenceChangeListener(this); + PreferenceHelper.registerOnSharedPreferenceChangeListener(this); } @Override @@ -242,7 +236,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen switchProvider.setVisibility(VISIBLE); switchProvider.setOnClickListener(v -> { closeDrawer(); - getActivity().startActivityForResult(new Intent(getActivity(), ProviderListActivity.class), REQUEST_CODE_SWITCH_PROVIDER); + getActivity().startActivityForResult(new Intent(getActivity(), SetupActivity.class), REQUEST_CODE_SWITCH_PROVIDER); }); } } @@ -260,7 +254,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen private void initSaveBatteryEntry() { saveBattery = drawerView.findViewById(R.id.battery_switch); saveBattery.showSubtitle(false); - saveBattery.setChecked(getSaveBattery(getContext())); + saveBattery.setChecked(getSaveBattery()); saveBattery.setOnCheckedChangeListener(((buttonView, isChecked) -> { if (!buttonView.isPressed()) { return; @@ -268,7 +262,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen if (isChecked) { showSaveBatteryAlert(); } else { - saveBattery(getContext(), false); + saveBattery(false); } })); boolean enableEntry = !TetheringObservable.getInstance().getTetheringState().isVpnTetheringRunning(); @@ -288,7 +282,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen return; } manualGatewaySelection = drawerView.findViewById(R.id.manualGatewaySelection); - String preferredGateway = getPreferredCity(getContext()); + String preferredGateway = getPreferredCity(); String subtitle = preferredGateway != null ? preferredGateway : getString(R.string.gateway_selection_recommended_location); manualGatewaySelection.setSubtitle(subtitle); boolean show = ProviderObservable.getInstance().getCurrentProvider().hasGatewaysInDifferentLocations(); @@ -388,7 +382,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen .setTitle(activity.getString(R.string.save_battery)) .setMessage(activity.getString(R.string.save_battery_message)) .setPositiveButton((android.R.string.yes), (dialog, which) -> { - saveBattery(getContext(), true); + saveBattery(true); }) .setNegativeButton(activity.getString(android.R.string.no), (dialog, which) -> saveBattery.setCheckedQuietly(false)) .setOnDismissListener(dialog -> showSaveBattery = false) @@ -417,7 +411,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen @Override public void onDestroy() { super.onDestroy(); - preferences.unregisterOnSharedPreferenceChangeListener(this); + PreferenceHelper.unregisterOnSharedPreferenceChangeListener(this); } public void refresh() { diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/ObfuscationProxyDialog.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/ObfuscationProxyDialog.java index 2c0fdd69..948d764f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/ObfuscationProxyDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/ObfuscationProxyDialog.java @@ -48,23 +48,23 @@ public class ObfuscationProxyDialog extends AppCompatDialogFragment { cancelButton = binding.buttonCancel; kcpSwitch = binding.kcpSwitch; - ipField.setText(PreferenceHelper.getObfuscationPinningIP(getContext())); - portField.setText(PreferenceHelper.getObfuscationPinningPort(getContext())); - certificateField.setText(PreferenceHelper.getObfuscationPinningCert(getContext())); - kcpSwitch.setChecked(PreferenceHelper.getObfuscationPinningKCP(getContext())); + ipField.setText(PreferenceHelper.getObfuscationPinningIP()); + portField.setText(PreferenceHelper.getObfuscationPinningPort()); + certificateField.setText(PreferenceHelper.getObfuscationPinningCert()); + kcpSwitch.setChecked(PreferenceHelper.getObfuscationPinningKCP()); GatewaysManager gatewaysManager = new GatewaysManager(getContext()); saveButton.setOnClickListener(v -> { String ip = TextUtils.isEmpty(ipField.getText()) ? null : ipField.getText().toString(); - PreferenceHelper.setObfuscationPinningIP(v.getContext(), ip); + PreferenceHelper.setObfuscationPinningIP(ip); String port = TextUtils.isEmpty(portField.getText()) ? null : portField.getText().toString(); - PreferenceHelper.setObfuscationPinningPort(v.getContext(), port); + PreferenceHelper.setObfuscationPinningPort(port); String cert = TextUtils.isEmpty(certificateField.getText()) ? null : certificateField.getText().toString(); - PreferenceHelper.setObfuscationPinningCert(v.getContext(), cert); - PreferenceHelper.setObfuscationPinningKCP(v.getContext(), kcpSwitch.isChecked()); - PreferenceHelper.setUseObfuscationPinning(v.getContext(), ip != null && port != null && cert != null); - PreferenceHelper.setObfuscationPinningGatewayLocation(v.getContext(), gatewaysManager.getLocationNameForIP(ip, v.getContext())); + PreferenceHelper.setObfuscationPinningCert(cert); + PreferenceHelper.setObfuscationPinningKCP(kcpSwitch.isChecked()); + PreferenceHelper.setUseObfuscationPinning(ip != null && port != null && cert != null); + PreferenceHelper.setObfuscationPinningGatewayLocation(gatewaysManager.getLocationNameForIP(ip, v.getContext())); dismiss(); }); @@ -78,8 +78,7 @@ public class ObfuscationProxyDialog extends AppCompatDialogFragment { cancelButton.setOnClickListener(v -> { boolean allowPinning = !TextUtils.isEmpty(ipField.getText()) && !TextUtils.isEmpty(portField.getText()) && !TextUtils.isEmpty(certificateField.getText()); - PreferenceHelper.setUseObfuscationPinning( - v.getContext(), allowPinning); + PreferenceHelper.setUseObfuscationPinning(allowPinning); dismiss(); }); diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java index f36b17ad..c8c994a5 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/SettingsFragment.java @@ -11,6 +11,7 @@ import static se.leap.bitmaskclient.base.models.Constants.USE_OBFUSCATION_PINNIN import static se.leap.bitmaskclient.base.utils.ConfigHelper.ObfsVpnHelper.useObfsVpn; import static se.leap.bitmaskclient.base.utils.ConfigHelper.isCalyxOSWithTetheringSupport; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.allowExperimentalTransports; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getExcludedApps; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferUDP; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getShowAlwaysOnDialog; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUseBridges; @@ -60,8 +61,6 @@ import se.leap.bitmaskclient.firewall.FirewallManager; public class SettingsFragment extends Fragment implements SharedPreferences.OnSharedPreferenceChangeListener { private FirewallManager firewallManager; - private SharedPreferences preferences; - private IconTextEntry tethering; private IconSwitchEntry firewall; IconSwitchEntry useUdpEntry; @@ -69,8 +68,7 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - preferences = PreferenceHelper.getSharedPreferences(getContext()); - preferences.registerOnSharedPreferenceChangeListener(this); + PreferenceHelper.registerOnSharedPreferenceChangeListener(this); firewallManager = new FirewallManager(getContext().getApplicationContext(), false); } @@ -94,20 +92,20 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh @Override public void onDestroy() { + PreferenceHelper.unregisterOnSharedPreferenceChangeListener(this); super.onDestroy(); - preferences.unregisterOnSharedPreferenceChangeListener(this); } private void initUseBridgesEntry(View rootView) { IconSwitchEntry useBridges = rootView.findViewById(R.id.bridges_switch); if (ProviderObservable.getInstance().getCurrentProvider().supportsPluggableTransports()) { useBridges.setVisibility(VISIBLE); - useBridges.setChecked(getUseBridges(getContext())); + useBridges.setChecked(getUseBridges()); useBridges.setOnCheckedChangeListener((buttonView, isChecked) -> { if (!buttonView.isPressed()) { return; } - useBridges(getContext(), isChecked); + useBridges(isChecked); if (VpnStatus.isVPNActive()) { EipCommand.startVPN(getContext(), false); Toast.makeText(getContext(), R.string.reconnecting, Toast.LENGTH_LONG).show(); @@ -116,7 +114,7 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh //We check the UI state of the useUdpEntry here as well, in order to avoid a situation //where both entries are disabled, because both preferences are enabled. //bridges can be enabled not only from here but also from error handling - boolean useUDP = getPreferUDP(getContext()) && useUdpEntry.isEnabled(); + boolean useUDP = getPreferUDP() && useUdpEntry.isEnabled(); useBridges.setEnabled(!useUDP); useBridges.setSubtitle(getString(useUDP ? R.string.disabled_while_udp_on : R.string.nav_drawer_subtitle_obfuscated_connection)); } else { @@ -127,12 +125,12 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh private void initUseSnowflakeEntry(View rootView) { IconSwitchEntry useSnowflake = rootView.findViewById(R.id.snowflake_switch); useSnowflake.setVisibility(VISIBLE); - useSnowflake.setChecked(hasSnowflakePrefs(getContext()) && getUseSnowflake(getContext())); + useSnowflake.setChecked(hasSnowflakePrefs() && getUseSnowflake()); useSnowflake.setOnCheckedChangeListener((buttonView, isChecked) -> { if (!buttonView.isPressed()) { return; } - useSnowflake(getContext(), isChecked); + useSnowflake(isChecked); }); } @@ -141,7 +139,7 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh IconTextEntry alwaysOnVpn = rootView.findViewById(R.id.always_on_vpn); alwaysOnVpn.setVisibility(VISIBLE); alwaysOnVpn.setOnClickListener((buttonView) -> { - if (getShowAlwaysOnDialog(getContext())) { + if (getShowAlwaysOnDialog()) { showAlwaysOnDialog(); } else { Intent intent = new Intent("android.net.vpn.SETTINGS"); @@ -155,18 +153,18 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh private void initPreferUDPEntry(View rootView) { useUdpEntry = rootView.findViewById(R.id.prefer_udp); useUdpEntry.setVisibility(VISIBLE); - useUdpEntry.setChecked(getPreferUDP(getContext())); + useUdpEntry.setChecked(getPreferUDP()); useUdpEntry.setOnCheckedChangeListener((buttonView, isChecked) -> { if (!buttonView.isPressed()) { return; } - preferUDP(getContext(), isChecked); + preferUDP(isChecked); if (VpnStatus.isVPNActive()) { EipCommand.startVPN(getContext(), false); Toast.makeText(getContext(), R.string.reconnecting, Toast.LENGTH_LONG).show(); } }); - boolean bridgesEnabled = getUseBridges(getContext()); + boolean bridgesEnabled = getUseBridges(); useUdpEntry.setEnabled(!bridgesEnabled); useUdpEntry.setSubtitle(getString(bridgesEnabled ? R.string.disabled_while_bridges_on : R.string.prefer_udp_subtitle)); } @@ -174,7 +172,7 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh private void initExcludeAppsEntry(View rootView) { IconTextEntry excludeApps = rootView.findViewById(R.id.exclude_apps); excludeApps.setVisibility(VISIBLE); - Set<String> apps = PreferenceHelper.getExcludedApps(this.getContext()); + Set<String> apps = getExcludedApps(); if (apps != null) { updateExcludeAppsSubtitle(excludeApps, apps.size()); } @@ -196,12 +194,12 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh private void initFirewallEntry(View rootView) { firewall = rootView.findViewById(R.id.enableIPv6Firewall); - firewall.setChecked(PreferenceHelper.useIpv6Firewall(getContext())); + firewall.setChecked(PreferenceHelper.useIpv6Firewall()); firewall.setOnCheckedChangeListener((buttonView, isChecked) -> { if (!buttonView.isPressed()) { return; } - PreferenceHelper.setUseIPv6Firewall(getContext(), isChecked); + PreferenceHelper.setUseIPv6Firewall(isChecked); if (VpnStatus.isVPNActive()) { if (isChecked) { firewallManager.startIPv6Firewall(); @@ -235,7 +233,7 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh if (context == null) { return; } - String pinnedGateway = PreferenceHelper.getPinnedGateway(rootView.getContext()); + String pinnedGateway = PreferenceHelper.getPinnedGateway(); gatewayPinning.setSubtitle(pinnedGateway != null ? pinnedGateway : "Connect to a specific Gateway for debugging purposes"); gatewayPinning.setOnClickListener(v -> { @@ -249,10 +247,10 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh if (gatewayPinningEditText.getText() != null) { String editTextInput = gatewayPinningEditText.getText().toString(); if (!TextUtils.isEmpty(editTextInput)) { - PreferenceHelper.setPreferredCity(context, null); - PreferenceHelper.pinGateway(context, editTextInput); + PreferenceHelper.setPreferredCity(null); + PreferenceHelper.pinGateway(editTextInput); } else { - PreferenceHelper.pinGateway(context, null); + PreferenceHelper.pinGateway(null); } } }) @@ -268,16 +266,16 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh return; } obfuscationPinning.setVisibility(VISIBLE); - boolean useBridges = getUseBridges(getContext()); + boolean useBridges = getUseBridges(); obfuscationPinning.setEnabled(useBridges); obfuscationPinning.setSubtitle(useBridges ? "Connect to a specific obfuscation proxy for debugging purposes" : "Enable Bridges to use this option"); - obfuscationPinning.setChecked(useObfuscationPinning(getContext())); + obfuscationPinning.setChecked(useObfuscationPinning()); obfuscationPinning.setOnCheckedChangeListener((buttonView, isChecked) -> { if (!buttonView.isPressed()) { return; } if (!isChecked) { - setUseObfuscationPinning(getContext(), false); + setUseObfuscationPinning(false); } else { showObfuscationPinningDialog(); } @@ -306,12 +304,12 @@ public class SettingsFragment extends Fragment implements SharedPreferences.OnSh IconSwitchEntry experimentalTransports = rootView.findViewById(R.id.experimental_transports); if (useObfsVpn() && ProviderObservable.getInstance().getCurrentProvider().supportsExperimentalPluggableTransports()) { experimentalTransports.setVisibility(VISIBLE); - experimentalTransports.setChecked(allowExperimentalTransports(this.getContext())); + experimentalTransports.setChecked(allowExperimentalTransports()); experimentalTransports.setOnCheckedChangeListener((buttonView, isChecked) -> { if (!buttonView.isPressed()) { return; } - setAllowExperimentalTransports(getContext(), isChecked); + setAllowExperimentalTransports(isChecked); }); } else { experimentalTransports.setVisibility(GONE); diff --git a/app/src/main/java/se/leap/bitmaskclient/base/fragments/TetheringDialog.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/TetheringDialog.java index e747d5b4..eb9d149f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/fragments/TetheringDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/TetheringDialog.java @@ -155,9 +155,9 @@ public class TetheringDialog extends AppCompatDialogFragment implements Observer builder.setView(view) .setPositiveButton(android.R.string.ok, (dialog, id) -> { - PreferenceHelper.allowWifiTethering(getContext(), dataset[0].checked); - PreferenceHelper.allowUsbTethering(getContext(), dataset[1].checked); - PreferenceHelper.allowBluetoothTethering(getContext(), dataset[2].checked); + PreferenceHelper.allowWifiTethering(dataset[0].checked); + PreferenceHelper.allowUsbTethering(dataset[1].checked); + PreferenceHelper.allowBluetoothTethering(dataset[2].checked); TetheringObservable.allowVpnWifiTethering(dataset[0].checked); TetheringObservable.allowVpnUsbTethering(dataset[1].checked); TetheringObservable.allowVpnBluetoothTethering(dataset[2].checked); @@ -247,15 +247,15 @@ public class TetheringDialog extends AppCompatDialogFragment implements Observer dataset = new DialogListAdapter.ViewModel[] { new DialogListAdapter.ViewModel(getContext().getResources().getDrawable(R.drawable.ic_wifi), getContext().getString(R.string.tethering_wifi), - PreferenceHelper.isWifiTetheringAllowed(getContext()), + PreferenceHelper.isWifiTetheringAllowed(), TetheringObservable.getInstance().isWifiTetheringEnabled()), new DialogListAdapter.ViewModel(getContext().getResources().getDrawable(R.drawable.ic_usb), getContext().getString(R.string.tethering_usb), - PreferenceHelper.isUsbTetheringAllowed(getContext()), + PreferenceHelper.isUsbTetheringAllowed(), TetheringObservable.getInstance().isUsbTetheringEnabled()), new DialogListAdapter.ViewModel(getContext().getResources().getDrawable(R.drawable.ic_bluetooth), getContext().getString(R.string.tethering_bluetooth), - PreferenceHelper.isBluetoothTetheringAllowed(getContext()), + PreferenceHelper.isBluetoothTetheringAllowed(), TetheringObservable.getInstance().isUsbTetheringEnabled()) }; } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java index 12196aee..b35a04cd 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/PreferenceHelper.java @@ -6,9 +6,14 @@ import static se.leap.bitmaskclient.base.models.Constants.ALLOW_TETHERING_BLUETO import static se.leap.bitmaskclient.base.models.Constants.ALLOW_TETHERING_USB; import static se.leap.bitmaskclient.base.models.Constants.ALLOW_TETHERING_WIFI; import static se.leap.bitmaskclient.base.models.Constants.ALWAYS_ON_SHOW_DIALOG; +import static se.leap.bitmaskclient.base.models.Constants.CLEARLOG; import static se.leap.bitmaskclient.base.models.Constants.DEFAULT_SHARED_PREFS_BATTERY_SAVER; +import static se.leap.bitmaskclient.base.models.Constants.EIP_IS_ALWAYS_ON; +import static se.leap.bitmaskclient.base.models.Constants.EIP_RESTART_ON_BOOT; import static se.leap.bitmaskclient.base.models.Constants.EXCLUDED_APPS; +import static se.leap.bitmaskclient.base.models.Constants.FIRST_TIME_USER_DATE; import static se.leap.bitmaskclient.base.models.Constants.GATEWAY_PINNING; +import static se.leap.bitmaskclient.base.models.Constants.LAST_DONATION_REMINDER_DATE; import static se.leap.bitmaskclient.base.models.Constants.LAST_UPDATE_CHECK; import static se.leap.bitmaskclient.base.models.Constants.LAST_USED_PROFILE; import static se.leap.bitmaskclient.base.models.Constants.OBFUSCATION_PINNING_CERT; @@ -16,6 +21,7 @@ import static se.leap.bitmaskclient.base.models.Constants.OBFUSCATION_PINNING_IP import static se.leap.bitmaskclient.base.models.Constants.OBFUSCATION_PINNING_KCP; import static se.leap.bitmaskclient.base.models.Constants.OBFUSCATION_PINNING_LOCATION; import static se.leap.bitmaskclient.base.models.Constants.OBFUSCATION_PINNING_PORT; +import static se.leap.bitmaskclient.base.models.Constants.PREFERENCES_APP_VERSION; import static se.leap.bitmaskclient.base.models.Constants.PREFERRED_CITY; import static se.leap.bitmaskclient.base.models.Constants.PREFER_UDP; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_CONFIGURED; @@ -40,7 +46,7 @@ import android.content.SharedPreferences; import android.text.TextUtils; import android.util.Log; -import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import androidx.annotation.WorkerThread; import androidx.security.crypto.EncryptedSharedPreferences; import androidx.security.crypto.MasterKey; @@ -57,6 +63,7 @@ import java.util.Map; import java.util.Set; import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.NativeUtils; import se.leap.bitmaskclient.BuildConfig; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.tor.TorStatusObservable; @@ -69,367 +76,493 @@ public class PreferenceHelper { private static final String TAG = PreferenceHelper.class.getSimpleName(); - public static SharedPreferences getSharedPreferences(Context context) { + private static SharedPreferences preferences; + private static final Object LOCK = new Object(); + + private SharedPreferences initSharedPreferences(Context appContext) { + Log.d(TAG, "getSharedPreferences is null"); + SharedPreferences preferences = null; try { - MasterKey masterKey = new MasterKey.Builder(context) + MasterKey masterKey = new MasterKey.Builder(appContext) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build(); - return EncryptedSharedPreferences.create( - context, + preferences = EncryptedSharedPreferences.create( + appContext, SHARED_ENCRYPTED_PREFERENCES, masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM); } catch (GeneralSecurityException | IOException e) { e.printStackTrace(); + preferences = appContext.getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); + } + + Log.d(TAG, "getSharedPreferences finished"); + return preferences; + } + + public PreferenceHelper(SharedPreferences preferences) { + if (!NativeUtils.isUnitTest()) { + throw new IllegalStateException("PreferenceHelper injected with shared preference outside of an unit test"); + } + synchronized (LOCK) { + PreferenceHelper.preferences = preferences; + } + } + public PreferenceHelper(Context context) { + synchronized (LOCK) { + preferences = initSharedPreferences(context.getApplicationContext()); + } + } + + public static void registerOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener) { + synchronized (LOCK) { + preferences.registerOnSharedPreferenceChangeListener(listener); } - return context.getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); } - public static Provider getSavedProviderFromSharedPreferences(@NonNull SharedPreferences preferences) { + public static void unregisterOnSharedPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener) { + synchronized (LOCK) { + preferences.unregisterOnSharedPreferenceChangeListener(listener); + } + } + + public static Provider getSavedProviderFromSharedPreferences() { Provider provider = new Provider(); - try { - provider.setMainUrl(new URL(preferences.getString(Provider.MAIN_URL, ""))); - provider.setProviderIp(preferences.getString(Provider.PROVIDER_IP, "")); - provider.setProviderApiIp(preferences.getString(Provider.PROVIDER_API_IP, "")); - provider.setGeoipUrl(preferences.getString(Provider.GEOIP_URL, "")); - provider.setMotdUrl(preferences.getString(Provider.MOTD_URL, "")); - provider.define(new JSONObject(preferences.getString(Provider.KEY, ""))); - provider.setCaCert(preferences.getString(Provider.CA_CERT, "")); - provider.setVpnCertificate(preferences.getString(PROVIDER_VPN_CERTIFICATE, "")); - provider.setPrivateKey(preferences.getString(PROVIDER_PRIVATE_KEY, "")); - provider.setEipServiceJson(new JSONObject(preferences.getString(PROVIDER_EIP_DEFINITION, ""))); - provider.setMotdJson(new JSONObject(preferences.getString(PROVIDER_MOTD, ""))); - provider.setLastMotdSeen(preferences.getLong(PROVIDER_MOTD_LAST_SEEN, 0L)); - provider.setLastMotdUpdate(preferences.getLong(PROVIDER_MOTD_LAST_UPDATED, 0L)); - provider.setMotdLastSeenHashes(preferences.getStringSet(PROVIDER_MOTD_HASHES, new HashSet<>())); - } catch (MalformedURLException | JSONException e) { - e.printStackTrace(); + synchronized (LOCK) { + try { + provider.setMainUrl(new URL(preferences.getString(Provider.MAIN_URL, ""))); + provider.setProviderIp(preferences.getString(Provider.PROVIDER_IP, "")); + provider.setProviderApiIp(preferences.getString(Provider.PROVIDER_API_IP, "")); + provider.setGeoipUrl(preferences.getString(Provider.GEOIP_URL, "")); + provider.setMotdUrl(preferences.getString(Provider.MOTD_URL, "")); + provider.define(new JSONObject(preferences.getString(Provider.KEY, ""))); + provider.setCaCert(preferences.getString(Provider.CA_CERT, "")); + provider.setVpnCertificate(preferences.getString(PROVIDER_VPN_CERTIFICATE, "")); + provider.setPrivateKey(preferences.getString(PROVIDER_PRIVATE_KEY, "")); + provider.setEipServiceJson(new JSONObject(preferences.getString(PROVIDER_EIP_DEFINITION, ""))); + provider.setMotdJson(new JSONObject(preferences.getString(PROVIDER_MOTD, ""))); + provider.setLastMotdSeen(preferences.getLong(PROVIDER_MOTD_LAST_SEEN, 0L)); + provider.setLastMotdUpdate(preferences.getLong(PROVIDER_MOTD_LAST_UPDATED, 0L)); + provider.setMotdLastSeenHashes(preferences.getStringSet(PROVIDER_MOTD_HASHES, new HashSet<>())); + } catch (MalformedURLException | JSONException e) { + e.printStackTrace(); + } } return provider; } - public static String getFromPersistedProvider(String toFetch, String providerDomain, SharedPreferences preferences) { - return preferences.getString(toFetch + "." + providerDomain, ""); + public static String getFromPersistedProvider(String toFetch, String providerDomain) { + synchronized (LOCK) { + return preferences.getString(toFetch + "." + providerDomain, ""); + } } - public static long getLongFromPersistedProvider(String toFetch, String providerDomain, SharedPreferences preferences) { - return preferences.getLong(toFetch + "." + providerDomain, 0L); + public static long getLongFromPersistedProvider(String toFetch, String providerDomain) { + synchronized (LOCK) { + return preferences.getLong(toFetch + "." + providerDomain, 0L); + } } - public static Set<String> getStringSetFromPersistedProvider(String toFetch, String providerDomain, SharedPreferences preferences) { - return preferences.getStringSet(toFetch + "." + providerDomain, new HashSet<>()); + public static Set<String> getStringSetFromPersistedProvider(String toFetch, String providerDomain) { + synchronized (LOCK) { + return preferences.getStringSet(toFetch + "." + providerDomain, new HashSet<>()); + } } - public static void persistProviderAsync(Context context, Provider provider) { - SharedPreferences preferences = getSharedPreferences(context); - storeProviderInPreferences(preferences, provider, true); + public static void persistProviderAsync(Provider provider) { + synchronized (LOCK) { + storeProviderInPreferences(provider, true); + } } - public static void storeProviderInPreferences(SharedPreferences preferences, Provider provider) { - storeProviderInPreferences(preferences, provider, false); + public static void storeProviderInPreferences(Provider provider) { + synchronized (LOCK) { + storeProviderInPreferences(provider, false); + } } // TODO: replace commit with apply after refactoring EIP //FIXME: don't save private keys in shared preferences! use the keystore - public static void storeProviderInPreferences(SharedPreferences preferences, Provider provider, boolean async) { - SharedPreferences.Editor editor = preferences.edit(); - editor.putBoolean(PROVIDER_CONFIGURED, true). - putString(Provider.PROVIDER_IP, provider.getProviderIp()). - putString(Provider.GEOIP_URL, provider.getGeoipUrl().toString()). - putString(Provider.MOTD_URL, provider.getMotdUrl().toString()). - putString(Provider.PROVIDER_API_IP, provider.getProviderApiIp()). - putString(Provider.MAIN_URL, provider.getMainUrlString()). - putString(Provider.KEY, provider.getDefinitionString()). - putString(Provider.CA_CERT, provider.getCaCert()). - putString(PROVIDER_EIP_DEFINITION, provider.getEipServiceJsonString()). - putString(PROVIDER_PRIVATE_KEY, provider.getPrivateKey()). - putString(PROVIDER_VPN_CERTIFICATE, provider.getVpnCertificate()). - putString(PROVIDER_MOTD, provider.getMotdJsonString()). - putStringSet(PROVIDER_MOTD_HASHES, provider.getMotdLastSeenHashes()). - putLong(PROVIDER_MOTD_LAST_SEEN, provider.getLastMotdSeen()). - putLong(PROVIDER_MOTD_LAST_UPDATED, provider.getLastMotdUpdate()); - if (async) { - editor.apply(); - } else { - editor.commit(); - } - - String providerDomain = provider.getDomain(); - preferences.edit().putBoolean(PROVIDER_CONFIGURED, true). - putString(Provider.PROVIDER_IP + "." + providerDomain, provider.getProviderIp()). - putString(Provider.PROVIDER_API_IP + "." + providerDomain, provider.getProviderApiIp()). - putString(Provider.MAIN_URL + "." + providerDomain, provider.getMainUrlString()). - putString(Provider.GEOIP_URL + "." + providerDomain, provider.getGeoipUrl().toString()). - putString(Provider.MOTD_URL + "." + providerDomain, provider.getMotdUrl().toString()). - putString(Provider.KEY + "." + providerDomain, provider.getDefinitionString()). - putString(Provider.CA_CERT + "." + providerDomain, provider.getCaCert()). - putString(PROVIDER_EIP_DEFINITION + "." + providerDomain, provider.getEipServiceJsonString()). - putString(PROVIDER_MOTD + "." + providerDomain, provider.getMotdJsonString()). - putStringSet(PROVIDER_MOTD_HASHES + "." + providerDomain, provider.getMotdLastSeenHashes()). - putLong(PROVIDER_MOTD_LAST_SEEN + "." + providerDomain, provider.getLastMotdSeen()). - putLong(PROVIDER_MOTD_LAST_UPDATED + "." + providerDomain, provider.getLastMotdUpdate()). - apply(); + public static void storeProviderInPreferences(Provider provider, boolean async) { + synchronized (LOCK) { + SharedPreferences.Editor editor = preferences.edit(); + editor.putBoolean(PROVIDER_CONFIGURED, true). + putString(Provider.PROVIDER_IP, provider.getProviderIp()). + putString(Provider.GEOIP_URL, provider.getGeoipUrl().toString()). + putString(Provider.MOTD_URL, provider.getMotdUrl().toString()). + putString(Provider.PROVIDER_API_IP, provider.getProviderApiIp()). + putString(Provider.MAIN_URL, provider.getMainUrlString()). + putString(Provider.KEY, provider.getDefinitionString()). + putString(Provider.CA_CERT, provider.getCaCert()). + putString(PROVIDER_EIP_DEFINITION, provider.getEipServiceJsonString()). + putString(PROVIDER_PRIVATE_KEY, provider.getPrivateKey()). + putString(PROVIDER_VPN_CERTIFICATE, provider.getVpnCertificate()). + putString(PROVIDER_MOTD, provider.getMotdJsonString()). + putStringSet(PROVIDER_MOTD_HASHES, provider.getMotdLastSeenHashes()). + putLong(PROVIDER_MOTD_LAST_SEEN, provider.getLastMotdSeen()). + putLong(PROVIDER_MOTD_LAST_UPDATED, provider.getLastMotdUpdate()); + if (async) { + editor.apply(); + } else { + editor.commit(); + } + + String providerDomain = provider.getDomain(); + preferences.edit().putBoolean(PROVIDER_CONFIGURED, true). + putString(Provider.PROVIDER_IP + "." + providerDomain, provider.getProviderIp()). + putString(Provider.PROVIDER_API_IP + "." + providerDomain, provider.getProviderApiIp()). + putString(Provider.MAIN_URL + "." + providerDomain, provider.getMainUrlString()). + putString(Provider.GEOIP_URL + "." + providerDomain, provider.getGeoipUrl().toString()). + putString(Provider.MOTD_URL + "." + providerDomain, provider.getMotdUrl().toString()). + putString(Provider.KEY + "." + providerDomain, provider.getDefinitionString()). + putString(Provider.CA_CERT + "." + providerDomain, provider.getCaCert()). + putString(PROVIDER_EIP_DEFINITION + "." + providerDomain, provider.getEipServiceJsonString()). + putString(PROVIDER_MOTD + "." + providerDomain, provider.getMotdJsonString()). + putStringSet(PROVIDER_MOTD_HASHES + "." + providerDomain, provider.getMotdLastSeenHashes()). + putLong(PROVIDER_MOTD_LAST_SEEN + "." + providerDomain, provider.getLastMotdSeen()). + putLong(PROVIDER_MOTD_LAST_UPDATED + "." + providerDomain, provider.getLastMotdUpdate()). + apply(); + } + } + + public static void putProviderString(String providerDomain, String key, String value) { + synchronized (LOCK) { + putString(key+"."+providerDomain, value); + } } /** * Sets the profile that is connected (to connect if the service restarts) */ - public static void setLastUsedVpnProfile(Context context, VpnProfile connectedProfile) { - SharedPreferences prefs = getSharedPreferences(context); - SharedPreferences.Editor prefsedit = prefs.edit(); - prefsedit.putString(LAST_USED_PROFILE, connectedProfile.toJson()); - prefsedit.apply(); + public static void setLastUsedVpnProfile(VpnProfile connectedProfile) { + synchronized (LOCK) { + preferences.edit().putString(LAST_USED_PROFILE, connectedProfile.toJson()).apply(); + } } /** * Returns the profile that was last connected (to connect if the service restarts) */ - public static VpnProfile getLastConnectedVpnProfile(Context context) { - SharedPreferences preferences = getSharedPreferences(context); - String lastConnectedProfileJson = preferences.getString(LAST_USED_PROFILE, null); + public static VpnProfile getLastConnectedVpnProfile() { + String lastConnectedProfileJson = null; + synchronized (LOCK) { + lastConnectedProfileJson = preferences.getString(LAST_USED_PROFILE, null); + } return VpnProfile.fromJson(lastConnectedProfileJson); } - public static void deleteProviderDetailsFromPreferences(@NonNull SharedPreferences preferences, String providerDomain) { - preferences.edit(). - remove(Provider.KEY + "." + providerDomain). - remove(Provider.CA_CERT + "." + providerDomain). - remove(Provider.PROVIDER_IP + "." + providerDomain). - remove(Provider.PROVIDER_API_IP + "." + providerDomain). - remove(Provider.MAIN_URL + "." + providerDomain). - remove(Provider.GEOIP_URL + "." + providerDomain). - remove(Provider.MOTD_URL + "." + providerDomain). - remove(PROVIDER_EIP_DEFINITION + "." + providerDomain). - remove(PROVIDER_PRIVATE_KEY + "." + providerDomain). - remove(PROVIDER_VPN_CERTIFICATE + "." + providerDomain). - remove(PROVIDER_MOTD + "." + providerDomain). - remove(PROVIDER_MOTD_HASHES + "." + providerDomain). - remove(PROVIDER_MOTD_LAST_SEEN + "." + providerDomain). - remove(PROVIDER_MOTD_LAST_UPDATED + "." + providerDomain). - apply(); - } - - public static void deleteCurrentProviderDetailsFromPreferences(@NonNull SharedPreferences preferences) { - preferences.edit(). - remove(Provider.KEY). - remove(Provider.CA_CERT). - remove(Provider.PROVIDER_IP). - remove(Provider.PROVIDER_API_IP). - remove(Provider.MAIN_URL). - remove(Provider.GEOIP_URL). - remove(Provider.MOTD_URL). - remove(PROVIDER_EIP_DEFINITION). - remove(PROVIDER_PRIVATE_KEY). - remove(PROVIDER_VPN_CERTIFICATE). - remove(PROVIDER_MOTD). - remove(PROVIDER_MOTD_HASHES). - remove(PROVIDER_MOTD_LAST_SEEN). - remove(PROVIDER_MOTD_LAST_UPDATED). - apply(); + public static void deleteProviderDetailsFromPreferences(String providerDomain) { + synchronized (LOCK) { + preferences.edit(). + remove(Provider.KEY + "." + providerDomain). + remove(Provider.CA_CERT + "." + providerDomain). + remove(Provider.PROVIDER_IP + "." + providerDomain). + remove(Provider.PROVIDER_API_IP + "." + providerDomain). + remove(Provider.MAIN_URL + "." + providerDomain). + remove(Provider.GEOIP_URL + "." + providerDomain). + remove(Provider.MOTD_URL + "." + providerDomain). + remove(PROVIDER_EIP_DEFINITION + "." + providerDomain). + remove(PROVIDER_PRIVATE_KEY + "." + providerDomain). + remove(PROVIDER_VPN_CERTIFICATE + "." + providerDomain). + remove(PROVIDER_MOTD + "." + providerDomain). + remove(PROVIDER_MOTD_HASHES + "." + providerDomain). + remove(PROVIDER_MOTD_LAST_SEEN + "." + providerDomain). + remove(PROVIDER_MOTD_LAST_UPDATED + "." + providerDomain). + apply(); + } + } + + public static void deleteCurrentProviderDetailsFromPreferences() { + synchronized (LOCK) { + preferences.edit(). + remove(Provider.KEY). + remove(Provider.CA_CERT). + remove(Provider.PROVIDER_IP). + remove(Provider.PROVIDER_API_IP). + remove(Provider.MAIN_URL). + remove(Provider.GEOIP_URL). + remove(Provider.MOTD_URL). + remove(PROVIDER_EIP_DEFINITION). + remove(PROVIDER_PRIVATE_KEY). + remove(PROVIDER_VPN_CERTIFICATE). + remove(PROVIDER_MOTD). + remove(PROVIDER_MOTD_HASHES). + remove(PROVIDER_MOTD_LAST_SEEN). + remove(PROVIDER_MOTD_LAST_UPDATED). + apply(); + } } // used in fatweb flavor @SuppressWarnings("unused") - public static void setLastAppUpdateCheck(Context context) { - putLong(context, LAST_UPDATE_CHECK, System.currentTimeMillis()); + public static void setLastAppUpdateCheck() { + putLong(LAST_UPDATE_CHECK, System.currentTimeMillis()); + } + + public static long getLastAppUpdateCheck() { + return getLong(LAST_UPDATE_CHECK, 0); + } + + public static void restartOnUpdate(boolean isEnabled) { + putBoolean(RESTART_ON_UPDATE, isEnabled); + } + + public static boolean getRestartOnUpdate() { + return getBoolean(RESTART_ON_UPDATE, false); + } + + public static boolean getRestartOnBoot() { + return getBoolean(EIP_RESTART_ON_BOOT, false); + } + + public static void restartOnBoot(boolean isEnabled) { + putBoolean(EIP_RESTART_ON_BOOT, isEnabled); } - public static long getLastAppUpdateCheck(Context context) { - return getLong(context, LAST_UPDATE_CHECK, 0); + public static void restartOnBootSync(boolean isEnabled) { + putBooleanSync(EIP_RESTART_ON_BOOT, isEnabled); } - public static void restartOnUpdate(Context context, boolean isEnabled) { - putBoolean(context, RESTART_ON_UPDATE, isEnabled); + public static void isAlwaysOnSync(boolean isEnabled) { + putBooleanSync(EIP_IS_ALWAYS_ON, isEnabled); } - public static boolean getRestartOnUpdate(Context context) { - return getBoolean(context, RESTART_ON_UPDATE, false); + public static boolean getIsAlwaysOn() { + return getBoolean(EIP_IS_ALWAYS_ON, false); } - public static boolean getPreferUDP(Context context) { - return getBoolean(context, PREFER_UDP, BuildConfig.prefer_udp); + public static void lastDonationReminderDate(String dateString) { + putString(LAST_DONATION_REMINDER_DATE, dateString); } - public static void preferUDP(Context context, boolean prefer) { - putBoolean(context, PREFER_UDP, prefer); + public static String getLastDonationReminderDate() { + return getString(LAST_DONATION_REMINDER_DATE, null); } - public static String getPinnedGateway(Context context) { - return getString(context, GATEWAY_PINNING, null); + public static void firstTimeUserDate(String dateString) { + putString(FIRST_TIME_USER_DATE, dateString); } - public static void pinGateway(Context context, String value) { - putString(context, GATEWAY_PINNING, value); + public static String getFirstTimeUserDate() { + return getString(FIRST_TIME_USER_DATE, null); } - public static boolean getUseBridges(SharedPreferences preferences) { - return preferences.getBoolean(USE_BRIDGES, false); + public static void setProviderVPNCertificate(String certificate) { + putString(PROVIDER_VPN_CERTIFICATE, certificate); + } + public static String getProviderVPNCertificate() { + return getString(PROVIDER_VPN_CERTIFICATE, ""); + } + + public static int getAppVersion() { + return getInt(PREFERENCES_APP_VERSION, -1); + } + + public static void setAppVersion(int version) { + putInt(PREFERENCES_APP_VERSION, version); + } + + public static boolean getClearLog() { + return getBoolean(CLEARLOG, true); + } + + public static void setClearLog(boolean clearLog) { + putBoolean(CLEARLOG, clearLog); } - public static boolean getUseBridges(Context context) { - return getBoolean(context, USE_BRIDGES, false); + public static boolean getPreferUDP() { + return getBoolean(PREFER_UDP, BuildConfig.prefer_udp); } - public static void useBridges(Context context, boolean isEnabled) { - putBoolean(context, USE_BRIDGES, isEnabled); + public static void preferUDP(boolean prefer) { + putBoolean(PREFER_UDP, prefer); } - public static Boolean getUseSnowflake(SharedPreferences preferences) { - return preferences.getBoolean(USE_SNOWFLAKE, true); + public static String getPinnedGateway() { + return getString(GATEWAY_PINNING, null); } - public static void useSnowflake(Context context, boolean isEnabled) { - putBoolean(context, USE_SNOWFLAKE, isEnabled); + public static void pinGateway(String value) { + putString(GATEWAY_PINNING, value); + } + + public static boolean getUseBridges() { + return getBoolean(USE_BRIDGES, false); + } + + public static void useBridges(boolean isEnabled) { + putBoolean(USE_BRIDGES, isEnabled); + } + + public static void useSnowflake(boolean isEnabled) { + putBoolean(USE_SNOWFLAKE, isEnabled); if (!isEnabled) { TorStatusObservable.setProxyPort(-1); } } - public static boolean hasSnowflakePrefs(SharedPreferences preferences) { - return preferences.contains(USE_SNOWFLAKE); + public static boolean hasSnowflakePrefs() { + return hasKey(USE_SNOWFLAKE); } - public static boolean hasSnowflakePrefs(Context context) { - return hasKey(context, USE_SNOWFLAKE); + public static Boolean getUseSnowflake() { + return getBoolean(USE_SNOWFLAKE, true); } - public static Boolean getUseSnowflake(Context context) { - return getBoolean(context, USE_SNOWFLAKE, true); + public static void saveBattery(boolean isEnabled) { + putBoolean(DEFAULT_SHARED_PREFS_BATTERY_SAVER, isEnabled); } - public static void saveBattery(Context context, boolean isEnabled) { - putBoolean(context, DEFAULT_SHARED_PREFS_BATTERY_SAVER, isEnabled); + public static boolean getSaveBattery() { + return getBoolean(DEFAULT_SHARED_PREFS_BATTERY_SAVER, false); } - public static boolean getSaveBattery(Context context) { - return getBoolean(context, DEFAULT_SHARED_PREFS_BATTERY_SAVER, false); + public static void allowUsbTethering(boolean isEnabled) { + putBoolean(ALLOW_TETHERING_USB, isEnabled); } - public static void allowUsbTethering(Context context, boolean isEnabled) { - putBoolean(context, ALLOW_TETHERING_USB, isEnabled); + public static boolean isUsbTetheringAllowed() { + return getBoolean(ALLOW_TETHERING_USB, false); } - public static boolean isUsbTetheringAllowed(Context context) { - return getBoolean(context, ALLOW_TETHERING_USB, false); + public static void allowWifiTethering(boolean isEnabled) { + putBoolean(ALLOW_TETHERING_WIFI, isEnabled); } - public static void allowWifiTethering(Context context, boolean isEnabled) { - putBoolean(context, ALLOW_TETHERING_WIFI, isEnabled); + public static boolean isWifiTetheringAllowed() { + return getBoolean(ALLOW_TETHERING_WIFI, false); } - public static boolean isWifiTetheringAllowed(Context context) { - return getBoolean(context, ALLOW_TETHERING_WIFI, false); + public static void allowBluetoothTethering(boolean isEnabled) { + putBoolean(ALLOW_TETHERING_BLUETOOTH, isEnabled); } - public static void allowBluetoothTethering(Context context, boolean isEnabled) { - putBoolean(context, ALLOW_TETHERING_BLUETOOTH, isEnabled); + public static boolean isBluetoothTetheringAllowed() { + return getBoolean(ALLOW_TETHERING_BLUETOOTH, false); } - public static boolean isBluetoothTetheringAllowed(Context context) { - return getBoolean(context, ALLOW_TETHERING_BLUETOOTH, false); + public static void setShowExperimentalFeatures(boolean show) { + putBoolean(SHOW_EXPERIMENTAL, show); } - public static void setShowExperimentalFeatures(Context context, boolean show) { - putBoolean(context, SHOW_EXPERIMENTAL, show); + public static boolean showExperimentalFeatures() { + return getBoolean(SHOW_EXPERIMENTAL, false); } - public static boolean showExperimentalFeatures(Context context) { - return getBoolean(context, SHOW_EXPERIMENTAL, false); + public static void setAllowExperimentalTransports(boolean show) { + putBoolean(ALLOW_EXPERIMENTAL_TRANSPORTS, show); } - public static void setAllowExperimentalTransports(Context context, boolean show) { - putBoolean(context, ALLOW_EXPERIMENTAL_TRANSPORTS, show); + public static boolean allowExperimentalTransports() { + return getBoolean(ALLOW_EXPERIMENTAL_TRANSPORTS, false); } - public static boolean allowExperimentalTransports(Context context) { - return getBoolean(context, ALLOW_EXPERIMENTAL_TRANSPORTS, false); + public static void setUseObfuscationPinning(Boolean pinning) { + putBoolean(USE_OBFUSCATION_PINNING, pinning); } - public static void setUseObfuscationPinning(Context context, Boolean pinning) { - putBoolean(context, USE_OBFUSCATION_PINNING, pinning); - } - - public static boolean useObfuscationPinning(Context context) { + public static boolean useObfuscationPinning() { return ConfigHelper.ObfsVpnHelper.useObfsVpn() && - getUseBridges(context) && - getBoolean(context, USE_OBFUSCATION_PINNING, false) && - !TextUtils.isEmpty(getObfuscationPinningIP(context)) && - !TextUtils.isEmpty(getObfuscationPinningCert(context)) && - !TextUtils.isEmpty(getObfuscationPinningPort(context)); + getUseBridges() && + getBoolean(USE_OBFUSCATION_PINNING, false) && + !TextUtils.isEmpty(getObfuscationPinningIP()) && + !TextUtils.isEmpty(getObfuscationPinningCert()) && + !TextUtils.isEmpty(getObfuscationPinningPort()); } - public static void setObfuscationPinningIP(Context context, String ip) { - putString(context, OBFUSCATION_PINNING_IP, ip); + public static void setObfuscationPinningIP(String ip) { + putString(OBFUSCATION_PINNING_IP, ip); } - public static String getObfuscationPinningIP(Context context) { - return getString(context, OBFUSCATION_PINNING_IP, null); + public static String getObfuscationPinningIP() { + return getString(OBFUSCATION_PINNING_IP, null); } - public static void setObfuscationPinningPort(Context context, String port) { - putString(context, OBFUSCATION_PINNING_PORT, port); + public static void setObfuscationPinningPort(String port) { + putString(OBFUSCATION_PINNING_PORT, port); } - public static String getObfuscationPinningPort(Context context) { - return getString(context, OBFUSCATION_PINNING_PORT, null); + public static String getObfuscationPinningPort() { + return getString(OBFUSCATION_PINNING_PORT, null); } - public static void setObfuscationPinningCert(Context context, String cert) { - putString(context, OBFUSCATION_PINNING_CERT, cert); + public static void setObfuscationPinningCert(String cert) { + putString(OBFUSCATION_PINNING_CERT, cert); } - public static String getObfuscationPinningCert(Context context) { - return getString(context, OBFUSCATION_PINNING_CERT, null); + public static String getObfuscationPinningCert() { + return getString(OBFUSCATION_PINNING_CERT, null); } - public static void setObfuscationPinningGatewayLocation(Context context, String location) { - putString(context, OBFUSCATION_PINNING_LOCATION, location); + public static void setObfuscationPinningGatewayLocation(String location) { + putString(OBFUSCATION_PINNING_LOCATION, location); } - public static String getObfuscationPinningGatewayLocation(Context context) { - return getString(context, OBFUSCATION_PINNING_LOCATION, null); + public static String getObfuscationPinningGatewayLocation() { + return getString(OBFUSCATION_PINNING_LOCATION, null); } - public static Boolean getObfuscationPinningKCP(Context context) { - return getBoolean(context, OBFUSCATION_PINNING_KCP, false); + public static Boolean getObfuscationPinningKCP() { + return getBoolean(OBFUSCATION_PINNING_KCP, false); } - public static void setObfuscationPinningKCP(Context context, boolean isKCP) { - putBoolean(context, OBFUSCATION_PINNING_KCP, isKCP); + public static void setObfuscationPinningKCP(boolean isKCP) { + putBoolean(OBFUSCATION_PINNING_KCP, isKCP); } - public static void setUseIPv6Firewall(Context context, boolean useFirewall) { - putBoolean(context, USE_IPv6_FIREWALL, useFirewall); + public static void setUseIPv6Firewall(boolean useFirewall) { + putBoolean(USE_IPv6_FIREWALL, useFirewall); } - public static boolean useIpv6Firewall(Context context) { - return getBoolean(context, USE_IPv6_FIREWALL, false); + public static boolean useIpv6Firewall() { + return getBoolean(USE_IPv6_FIREWALL, false); } - public static void saveShowAlwaysOnDialog(Context context, boolean showAlwaysOnDialog) { - putBoolean(context, ALWAYS_ON_SHOW_DIALOG, showAlwaysOnDialog); + public static void saveShowAlwaysOnDialog(boolean showAlwaysOnDialog) { + putBoolean(ALWAYS_ON_SHOW_DIALOG, showAlwaysOnDialog); } - public static boolean getShowAlwaysOnDialog(Context context) { - return getBoolean(context, ALWAYS_ON_SHOW_DIALOG, true); + public static boolean getShowAlwaysOnDialog() { + return getBoolean(ALWAYS_ON_SHOW_DIALOG, true); } - public static String getPreferredCity(Context context) { - return useObfuscationPinning(context) ? null : getString(context, PREFERRED_CITY, null); + public static String getPreferredCity() { + return useObfuscationPinning() ? null : getString(PREFERRED_CITY, null); } @WorkerThread - public static void setPreferredCity(Context context, String city) { - putStringSync(context, PREFERRED_CITY, city); + public static void setPreferredCity(String city) { + putStringSync(PREFERRED_CITY, city); } + @VisibleForTesting public static JSONObject getEipDefinitionFromPreferences(SharedPreferences preferences) { JSONObject result = new JSONObject(); + String eipDefinitionString = ""; + try { + synchronized (LOCK) { + eipDefinitionString = preferences.getString(PROVIDER_EIP_DEFINITION, ""); + } + if (!eipDefinitionString.isEmpty()) { + result = new JSONObject(eipDefinitionString); + } + } catch (JSONException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return result; + } + + public static JSONObject getEipDefinitionFromPreferences() { + JSONObject result = new JSONObject(); + String eipDefinitionString = ""; try { - String eipDefinitionString = preferences.getString(PROVIDER_EIP_DEFINITION, ""); + synchronized (LOCK) { + eipDefinitionString = preferences.getString(PROVIDER_EIP_DEFINITION, ""); + } if (!eipDefinitionString.isEmpty()) { result = new JSONObject(eipDefinitionString); } @@ -440,125 +573,121 @@ public class PreferenceHelper { return result; } - public static void setExcludedApps(Context context, Set<String> apps) { - putStringSet(context, EXCLUDED_APPS, apps); + public static void setExcludedApps(Set<String> apps) { + putStringSet(EXCLUDED_APPS, apps); } - public static Set<String> getExcludedApps(Context context) { - if (context == null) { - return null; + public static Set<String> getExcludedApps() { + synchronized (LOCK) { + return preferences.getStringSet(EXCLUDED_APPS, new HashSet<>()); } - SharedPreferences preferences = getSharedPreferences(context); - return preferences.getStringSet(EXCLUDED_APPS, new HashSet<>()); } - public static long getLong(Context context, String key, long defValue) { - if (context == null) { - return defValue; + public static long getLong(String key, long defValue) { + synchronized (LOCK) { + return preferences.getLong(key, defValue); } - SharedPreferences preferences = getSharedPreferences(context); - return preferences.getLong(key, defValue); } - public static void putLong(Context context, String key, long value) { - if (context == null) { - return; + public static void putLong(String key, long value) { + synchronized (LOCK) { + preferences.edit().putLong(key, value).apply(); } - SharedPreferences preferences = getSharedPreferences(context); - preferences.edit().putLong(key, value).apply(); } - public static String getString(Context context, String key, String defValue) { - if (context == null) { - return defValue; + public static int getInt(String key, int defValue) { + synchronized (LOCK) { + return preferences.getInt(key, defValue); } - SharedPreferences preferences = getSharedPreferences(context); - return preferences.getString(key, defValue); } - @WorkerThread - public static void putStringSync(Context context, String key, String value) { - if (context == null) { - return; + public static void putInt(String key, int value) { + synchronized (LOCK) { + preferences.edit().putInt(key, value).apply(); } - SharedPreferences preferences = getSharedPreferences(context); - preferences.edit().putString(key, value).commit(); } - public static void putString(Context context, String key, String value) { - if (context == null) { - return; + public static String getString(String key, String defValue) { + synchronized (LOCK) { + return preferences.getString(key, defValue); } - SharedPreferences preferences = getSharedPreferences(context); - preferences.edit().putString(key, value).apply(); } - public static void putStringSet(Context context, String key, Set<String> value) { - if (context == null) { - return; + @WorkerThread + public static void putStringSync(String key, String value) { + synchronized (LOCK) { + preferences.edit().putString(key, value).commit(); } - SharedPreferences preferences = getSharedPreferences(context); - preferences.edit().putStringSet(key, value).apply(); } - public static boolean getBoolean(Context context, String key, Boolean defValue) { - if (context == null) { - return false; + public static void putString(String key, String value) { + synchronized (LOCK) { + preferences.edit().putString(key, value).apply(); } - - SharedPreferences preferences = getSharedPreferences(context); - return preferences.getBoolean(key, defValue); } - public static void putBoolean(Context context, String key, Boolean value) { - if (context == null) { - return; + public static void putStringSet(String key, Set<String> value) { + synchronized (LOCK) { + preferences.edit().putStringSet(key, value).apply(); } + } - SharedPreferences preferences = getSharedPreferences(context); - preferences.edit().putBoolean(key, value).apply(); + public static boolean getBoolean(String key, Boolean defValue) { + synchronized (LOCK) { + return preferences.getBoolean(key, defValue); + } } - private static Boolean hasKey(Context context, String key) { - if (context == null) { - return false; + public static void putBoolean(String key, Boolean value) { + synchronized (LOCK) { + preferences.edit().putBoolean(key, value).apply(); } + } - SharedPreferences preferences = getSharedPreferences(context); - return preferences.contains(key); + public static void putBooleanSync(String key, Boolean value) { + synchronized (LOCK) { + preferences.edit().putBoolean(key, value).commit(); + } } - public static void migrateToEncryptedPrefs(Context context) { - SharedPreferences encryptedPrefs = getSharedPreferences(context); - if (!(encryptedPrefs instanceof EncryptedSharedPreferences)) { - Log.e(TAG, "Failed to migrate shared preferences"); - return; + public static Boolean hasKey(String key) { + synchronized (LOCK) { + return preferences.contains(key); } - SharedPreferences.Editor encryptedEditor = encryptedPrefs.edit(); - SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); - Map<String,?> keys = preferences.getAll(); + } - for(Map.Entry<String,?> entry : keys.entrySet()){ - try { - Object value = entry.getValue(); - if (value instanceof String) { - encryptedEditor.putString(entry.getKey(), (String) value); - } else if (value instanceof Boolean) { - encryptedEditor.putBoolean(entry.getKey(), (Boolean) value); - } else if (value instanceof Integer) { - encryptedEditor.putInt(entry.getKey(), (Integer) value); - } else if (value instanceof Set<?>) { - encryptedEditor.putStringSet(entry.getKey(), (Set<String>) value); - } else if (value instanceof Long) { - encryptedEditor.putLong(entry.getKey(), (Long) value); - } else if (value instanceof Float) { - encryptedEditor.putFloat(entry.getKey(), (Float) value); + public static void migrateToEncryptedPrefs(Context context) { + synchronized (LOCK) { + if (!(preferences instanceof EncryptedSharedPreferences)) { + Log.e(TAG, "Failed to migrate shared preferences"); + return; + } + SharedPreferences.Editor encryptedEditor = preferences.edit(); + SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); + Map<String,?> keys = preferences.getAll(); + + for(Map.Entry<String,?> entry : keys.entrySet()){ + try { + Object value = entry.getValue(); + if (value instanceof String) { + encryptedEditor.putString(entry.getKey(), (String) value); + } else if (value instanceof Boolean) { + encryptedEditor.putBoolean(entry.getKey(), (Boolean) value); + } else if (value instanceof Integer) { + encryptedEditor.putInt(entry.getKey(), (Integer) value); + } else if (value instanceof Set<?>) { + encryptedEditor.putStringSet(entry.getKey(), (Set<String>) value); + } else if (value instanceof Long) { + encryptedEditor.putLong(entry.getKey(), (Long) value); + } else if (value instanceof Float) { + encryptedEditor.putFloat(entry.getKey(), (Float) value); + } + } catch (ClassCastException e) { + e.printStackTrace(); } - } catch (ClassCastException e) { - e.printStackTrace(); } + encryptedEditor.commit(); + preferences.edit().clear().apply(); + } } - encryptedEditor.commit(); - preferences.edit().clear().apply(); - } } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/utils/ViewHelper.java b/app/src/main/java/se/leap/bitmaskclient/base/utils/ViewHelper.java index 51bcb2b1..ebf26048 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/utils/ViewHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/utils/ViewHelper.java @@ -1,11 +1,14 @@ package se.leap.bitmaskclient.base.utils; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + +import android.animation.Animator; +import android.animation.ValueAnimator; import android.app.Activity; -import android.app.Notification; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; -import android.os.Build; import android.text.Spannable; import android.text.SpannableString; import android.text.style.ForegroundColorSpan; @@ -13,13 +16,14 @@ import android.util.Log; import android.view.View; import android.view.Window; import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; import androidx.annotation.ColorRes; import androidx.annotation.DimenRes; +import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.AppCompatTextView; import androidx.core.content.ContextCompat; import androidx.fragment.app.Fragment; @@ -146,4 +150,65 @@ public class ViewHelper { bar.setTitle(spannableTitle); } + public interface AnimationInterface { + void onAnimationEnd(); + } + + public static void animateContainerVisibility(View container, boolean isExpanded) { + animateContainerVisibility(container, isExpanded, null); + } + + public static void animateContainerVisibility(View container, boolean isExpanded, AnimationInterface animationInterface) { + + int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + + container.measure(widthMeasureSpec, heightMeasureSpec); + int measuredHeight = container.getMeasuredHeight(); + + int targetHeight = isExpanded ? 0 : measuredHeight; // Get the actual content height of the view + int initialHeight = isExpanded ? measuredHeight : 0; + + ValueAnimator animator = ValueAnimator.ofInt(initialHeight, targetHeight); + animator.setDuration(250); // Set the duration of the animation in milliseconds + + animator.addUpdateListener(animation -> { + container.getLayoutParams().height = (int) animation.getAnimatedValue(); + container.requestLayout(); + }); + animator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(@NonNull Animator animation) { + if (initialHeight == 0 && container.getVisibility() == GONE) { + container.setVisibility(VISIBLE); + } + } + + @Override + public void onAnimationEnd(@NonNull Animator animation) { + if (targetHeight == 0) { + container.setVisibility(GONE); + } + if (animationInterface != null) { + animationInterface.onAnimationEnd(); + } + } + + @Override + public void onAnimationCancel(@NonNull Animator animation) { + container.setVisibility(targetHeight == 0 ? GONE : VISIBLE); + } + + @Override + public void onAnimationRepeat(@NonNull Animator animation) {} + }); + + animator.start(); + } + + public static void hideKeyboardFrom(Context context, View view) { + InputMethodManager imm = (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/ActionBarTitle.java b/app/src/main/java/se/leap/bitmaskclient/base/views/ActionBarTitle.java index a151305e..3aa21ae9 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/views/ActionBarTitle.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/views/ActionBarTitle.java @@ -1,10 +1,11 @@ package se.leap.bitmaskclient.base.views; import android.content.Context; +import android.graphics.Typeface; +import android.os.Build; import android.util.AttributeSet; import android.view.Gravity; import android.view.LayoutInflater; -import android.widget.RelativeLayout; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; @@ -90,7 +91,16 @@ public class ActionBarTitle extends LinearLayoutCompat { actionBarTitle.setLayoutParams(titleLayoutParams); actionBarSubtitle.setLayoutParams(subtitleLayoutParams); container.setLayoutParams(containerLayoutParams); + } - + public void setSingleBoldTitle() { + showSubtitle(false); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + actionBarTitle.setTypeface(Typeface.create(null,900,false)); + } else { + actionBarTitle.setTypeface(actionBarTitle.getTypeface(), Typeface.BOLD); + } + actionBarTitle.setLetterSpacing(0.05f); + actionBarTitle.setTextSize(24f); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java b/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java index c7273613..e322e6c6 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java @@ -2,10 +2,13 @@ package se.leap.bitmaskclient.base.views; import android.annotation.TargetApi; import android.content.Context; +import android.graphics.PorterDuff; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.LayoutInflater; import android.widget.RelativeLayout; +import androidx.annotation.DrawableRes; import androidx.appcompat.widget.AppCompatImageView; import androidx.core.content.ContextCompat; @@ -56,4 +59,14 @@ public class MainButton extends RelativeLayout { button.setTag(isOn ? "button_circle_stop" : "button_circle_start"); } } + + public void setCustomDrawable(@DrawableRes int drawableResource) { + Drawable drawable = ContextCompat.getDrawable(getContext(), drawableResource); + if (drawable == null) { + return; + } + + button.setImageDrawable(drawable); + button.setTag("button_setup_circle_custom"); + } } diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/ProgressSpinner.java b/app/src/main/java/se/leap/bitmaskclient/base/views/ProgressSpinner.java new file mode 100644 index 00000000..380ddf23 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/views/ProgressSpinner.java @@ -0,0 +1,63 @@ +package se.leap.bitmaskclient.base.views; + +import android.annotation.TargetApi; +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.RelativeLayout; + +import androidx.appcompat.widget.AppCompatImageView; +import androidx.appcompat.widget.AppCompatTextView; +import androidx.core.content.ContextCompat; + +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.databinding.VProgressSpinnerBinding; + +public class ProgressSpinner extends RelativeLayout { + + private static final String TAG = ProgressSpinner.class.getSimpleName(); + + AppCompatImageView spinnerView; + AppCompatTextView textView; + + public ProgressSpinner(Context context) { + super(context); + initLayout(context); + } + + public ProgressSpinner(Context context, AttributeSet attrs) { + super(context, attrs); + initLayout(context); + } + + public ProgressSpinner(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initLayout(context); + } + + + public ProgressSpinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initLayout(context); + } + + private void initLayout(Context context) { + VProgressSpinnerBinding binding = VProgressSpinnerBinding.inflate(LayoutInflater.from(context), this, true); + spinnerView = binding.spinnerView; + textView = binding.tvProgress; + } + + public void update(int progress) { + String text = ""; + if (progress > 0) { + if ((progress / 10) == 0) { + text = text + " "; + } + if ((progress / 100) == 0) { + text = text + " "; + } + text = text + progress + "%"; + } + textView.setText(text); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java index 29714670..d8905bca 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -22,7 +22,6 @@ import static se.leap.bitmaskclient.R.string.vpn_certificate_is_invalid; import static se.leap.bitmaskclient.R.string.warning_client_parsing_error_gateways; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; -import static se.leap.bitmaskclient.base.models.Constants.CLEARLOG; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_CHECK_CERT_VALIDITY; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_IS_RUNNING; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_LAUNCH_VPN; @@ -50,7 +49,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.content.SharedPreferences; import android.net.VpnService; import android.os.Build; import android.os.Bundle; @@ -81,7 +79,6 @@ 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.Preferences; import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.OnBootReceiver; @@ -108,7 +105,6 @@ public final class EIP extends JobIntentService implements Observer { ERRORS = "errors", ERRORID = "errorId"; - private volatile SharedPreferences preferences; private volatile EipStatus eipStatus; // Service connection to OpenVpnService, shared between threads private volatile OpenVpnServiceConnection openVpnServiceConnection; @@ -144,7 +140,6 @@ public final class EIP extends JobIntentService implements Observer { super.onCreate(); eipStatus = EipStatus.getInstance(); eipStatus.addObserver(this); - preferences = PreferenceHelper.getSharedPreferences(this); } @Override @@ -230,8 +225,9 @@ public final class EIP extends JobIntentService implements Observer { earlyRoutes(result); } - if (!preferences.getBoolean(EIP_RESTART_ON_BOOT, false)) { - preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, true).commit(); + if (!PreferenceHelper.getRestartOnBoot()) { + // TODO: check if move to async here was ok + PreferenceHelper.restartOnBoot(true); } if (!isVPNCertificateValid()) { @@ -328,7 +324,7 @@ public final class EIP extends JobIntentService implements Observer { if (gatewayOptions == null || gatewayOptions.gateway == null || (profile = gatewayOptions.gateway.getProfile(gatewayOptions.transportType)) == null) { - String preferredLocation = getPreferredCity(getApplicationContext()); + String preferredLocation = getPreferredCity(); if (preferredLocation != null) { setErrorResult(result, NO_MORE_GATEWAYS.toString(), getStringResourceForNoMoreGateways(), getString(R.string.app_name), preferredLocation); } else { @@ -354,8 +350,9 @@ public final class EIP extends JobIntentService implements Observer { LocalBroadcastManager.getInstance(this).sendBroadcast(setupObserverIntent); // Check if we need to clear the log - if (Preferences.getDefaultSharedPreferences(this).getBoolean(CLEARLOG, true)) + if (PreferenceHelper.getClearLog()) { VpnStatus.clearLog(); + } // check profile configuration int vpnok = profile.checkProfile(this); @@ -421,12 +418,12 @@ public final class EIP extends JobIntentService implements Observer { * @return true if VPN certificate is valid false otherwise */ private boolean isVPNCertificateValid() { - VpnCertificateValidator validator = new VpnCertificateValidator(preferences.getString(PROVIDER_VPN_CERTIFICATE, "")); + VpnCertificateValidator validator = new VpnCertificateValidator(PreferenceHelper.getProviderVPNCertificate()); return validator.isValid(); } private boolean shouldUpdateVPNCertificate() { - VpnCertificateValidator validator = new VpnCertificateValidator(preferences.getString(PROVIDER_VPN_CERTIFICATE, "")); + VpnCertificateValidator validator = new VpnCertificateValidator(PreferenceHelper.getProviderVPNCertificate()); return validator.shouldBeUpdated(); } @@ -476,7 +473,7 @@ public final class EIP extends JobIntentService implements Observer { * then stop VPN */ private boolean stop() { - preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, false).apply(); + PreferenceHelper.restartOnBoot(false); if (eipStatus.isBlockingVpnEstablished()) { stopBlockingVpn(); } @@ -549,11 +546,11 @@ public final class EIP extends JobIntentService implements Observer { private @StringRes int getStringResourceForNoMoreGateways() { - boolean isManualGatewaySelection = PreferenceHelper.getPreferredCity(getApplicationContext()) != null; + boolean isManualGatewaySelection = PreferenceHelper.getPreferredCity() != null; if (isManualGatewaySelection) { return R.string.warning_no_more_gateways_manual_gw_selection; } else if (ProviderObservable.getInstance().getCurrentProvider().supportsPluggableTransports()) { - if (PreferenceHelper.getUseBridges(getApplicationContext())) { + if (PreferenceHelper.getUseBridges()) { return R.string.warning_no_more_gateways_use_ovpn; } else { return R.string.warning_no_more_gateways_use_pt; diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java index bb05810c..ed83770b 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EipSetupObserver.java @@ -56,7 +56,6 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.SharedPreferences; import android.os.Bundle; import android.util.Log; @@ -98,12 +97,10 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta AtomicBoolean changingGateway = new AtomicBoolean(false); AtomicInteger setupNClosestGateway = new AtomicInteger(); private Vector<EipSetupListener> listeners = new Vector<>(); - private SharedPreferences preferences; private static EipSetupObserver instance; - private EipSetupObserver(Context context, SharedPreferences preferences) { + private EipSetupObserver(Context context) { this.appContext = context.getApplicationContext(); - this.preferences = preferences; IntentFilter updateIntentFilter = new IntentFilter(BROADCAST_GATEWAY_SETUP_OBSERVER_EVENT); updateIntentFilter.addAction(BROADCAST_EIP_EVENT); updateIntentFilter.addAction(BROADCAST_PROVIDER_API_EVENT); @@ -115,9 +112,9 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta VpnStatus.addLogListener(this); } - public static void init(Context context, SharedPreferences preferences) { + public static void init(Context context) { if (instance == null) { - instance = new EipSetupObserver(context, preferences); + instance = new EipSetupObserver(context); } } @@ -196,7 +193,7 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta Log.d(TAG, "correctly updated service json"); provider = resultData.getParcelable(PROVIDER_KEY); ProviderObservable.getInstance().updateProvider(provider); - PreferenceHelper.storeProviderInPreferences(preferences, provider); + PreferenceHelper.storeProviderInPreferences(provider); if (EipStatus.getInstance().isDisconnected()) { EipCommand.startVPN(appContext, false); } @@ -204,7 +201,7 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta case CORRECTLY_UPDATED_INVALID_VPN_CERTIFICATE: provider = resultData.getParcelable(PROVIDER_KEY); ProviderObservable.getInstance().updateProvider(provider); - PreferenceHelper.storeProviderInPreferences(preferences, provider); + PreferenceHelper.storeProviderInPreferences(provider); EipCommand.startVPN(appContext, false); EipStatus.getInstance().setUpdatingVpnCert(false); if (TorStatusObservable.isRunning()) { @@ -214,7 +211,7 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta case CORRECTLY_DOWNLOADED_GEOIP_JSON: provider = resultData.getParcelable(PROVIDER_KEY); ProviderObservable.getInstance().updateProvider(provider); - PreferenceHelper.storeProviderInPreferences(preferences, provider); + PreferenceHelper.storeProviderInPreferences(provider); maybeStartEipService(resultData); break; case INCORRECTLY_DOWNLOADED_GEOIP_JSON: @@ -400,7 +397,7 @@ public class EipSetupObserver extends BroadcastReceiver implements VpnStatus.Sta } private boolean shouldCheckAppUpdate() { - return System.currentTimeMillis() - PreferenceHelper.getLastAppUpdateCheck(appContext) >= UPDATE_CHECK_TIMEOUT; + return System.currentTimeMillis() - PreferenceHelper.getLastAppUpdateCheck() >= UPDATE_CHECK_TIMEOUT; } private void selectNextGateway() { diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java index 719b960e..d2592cd7 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java @@ -37,8 +37,6 @@ import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getObfuscationPi import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferUDP; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.useObfuscationPinning; -import android.content.Context; - import androidx.annotation.NonNull; import com.google.gson.Gson; @@ -86,12 +84,12 @@ public class Gateway { * Build a gateway object from a JSON OpenVPN gateway definition in eip-service.json * and create a VpnProfile belonging to it. */ - public Gateway(JSONObject eipDefinition, JSONObject secrets, JSONObject gateway, Context context) + public Gateway(JSONObject eipDefinition, JSONObject secrets, JSONObject gateway) throws ConfigParser.ConfigParseError, JSONException, IOException { - this(eipDefinition, secrets, gateway, null, context); + this(eipDefinition, secrets, gateway, null); } - public Gateway(JSONObject eipDefinition, JSONObject secrets, JSONObject gateway, JSONObject load, Context context) + public Gateway(JSONObject eipDefinition, JSONObject secrets, JSONObject gateway, JSONObject load) throws ConfigParser.ConfigParseError, JSONException, IOException { this.gateway = gateway; @@ -99,28 +97,28 @@ public class Gateway { this.load = load; apiVersion = getApiVersion(eipDefinition); - VpnConfigGenerator.Configuration configuration = getProfileConfig(context, eipDefinition, apiVersion); + VpnConfigGenerator.Configuration configuration = getProfileConfig(eipDefinition, apiVersion); generalConfiguration = getGeneralConfiguration(eipDefinition); timezone = getTimezone(eipDefinition); name = configuration.profileName; vpnProfiles = createVPNProfiles(configuration); } - private VpnConfigGenerator.Configuration getProfileConfig(Context context, JSONObject eipDefinition, int apiVersion) { + private VpnConfigGenerator.Configuration getProfileConfig(JSONObject eipDefinition, int apiVersion) { VpnConfigGenerator.Configuration config = new VpnConfigGenerator.Configuration(); config.apiVersion = apiVersion; - config.preferUDP = getPreferUDP(context); - config.experimentalTransports = allowExperimentalTransports(context); - config.excludedApps = getExcludedApps(context); + config.preferUDP = getPreferUDP(); + config.experimentalTransports = allowExperimentalTransports(); + config.excludedApps = getExcludedApps(); - config.remoteGatewayIP = config.useObfuscationPinning ? getObfuscationPinningIP(context) : gateway.optString(IP_ADDRESS); - config.useObfuscationPinning = useObfuscationPinning(context); - config.profileName = config.useObfuscationPinning ? getObfuscationPinningGatewayLocation(context) : locationAsName(eipDefinition); + config.remoteGatewayIP = config.useObfuscationPinning ? getObfuscationPinningIP() : gateway.optString(IP_ADDRESS); + config.useObfuscationPinning = useObfuscationPinning(); + config.profileName = config.useObfuscationPinning ? getObfuscationPinningGatewayLocation() : locationAsName(eipDefinition); if (config.useObfuscationPinning) { - config.obfuscationProxyIP = getObfuscationPinningIP(context); - config.obfuscationProxyPort = getObfuscationPinningPort(context); - config.obfuscationProxyCert = getObfuscationPinningCert(context); - config.obfuscationProxyKCP = getObfuscationPinningKCP(context); + config.obfuscationProxyIP = getObfuscationPinningIP(); + config.obfuscationProxyPort = getObfuscationPinningPort(); + config.obfuscationProxyCert = getObfuscationPinningCert(); + config.obfuscationProxyKCP = getObfuscationPinningKCP(); } return config; } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java index 5e05b7c1..9b4d431c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -134,7 +134,7 @@ public class GatewaysManager { * @return the n closest Gateway */ public GatewayOptions select(int nClosest) { - if (PreferenceHelper.useObfuscationPinning(context)) { + if (PreferenceHelper.useObfuscationPinning()) { if (nClosest > 2) { // no need to try again the pinned proxy, probably configuration error return null; @@ -145,12 +145,12 @@ public class GatewaysManager { } return new GatewayOptions(gateway, OBFS4); } - String selectedCity = getPreferredCity(context); + String selectedCity = getPreferredCity(); return select(nClosest, selectedCity); } public GatewayOptions select(int nClosest, String city) { - TransportType[] transportTypes = getUseBridges(context) ? new TransportType[]{OBFS4, OBFS4_HOP} : new TransportType[]{OPENVPN}; + TransportType[] transportTypes = getUseBridges() ? new TransportType[]{OBFS4, OBFS4_HOP} : new TransportType[]{OPENVPN}; if (presortedList.size() > 0) { return getGatewayFromPresortedList(nClosest, transportTypes, city); } @@ -193,7 +193,7 @@ public class GatewaysManager { HashMap<String, Integer> locationNames = new HashMap<>(); ArrayList<Location> locations = new ArrayList<>(); - String preferredCity = PreferenceHelper.getPreferredCity(context); + String preferredCity = PreferenceHelper.getPreferredCity(); for (Gateway gateway : gateways.values()) { String name = gateway.getName(); if (name == null) { @@ -399,16 +399,18 @@ public class GatewaysManager { e.printStackTrace(); } - if (PreferenceHelper.useObfuscationPinning(context)) { + if (PreferenceHelper.useObfuscationPinning()) { try { Transport[] transports = new Transport[]{ new Transport(OBFS4.toString(), - new String[]{getObfuscationPinningKCP(context) ? "kcp" : "tcp"}, - new String[]{getObfuscationPinningPort(context)}, - getObfuscationPinningCert(context))}; + new String[]{getObfuscationPinningKCP() ? "kcp" : "tcp"}, + new String[]{getObfuscationPinningPort()}, + getObfuscationPinningCert())}; GatewayJson.Capabilities capabilities = new GatewayJson.Capabilities(false, false, false, transports, false); - GatewayJson gatewayJson = new GatewayJson(context.getString(R.string.unknown_location), getObfuscationPinningIP(context), null, PINNED_OBFUSCATION_PROXY, capabilities); - Gateway gateway = new Gateway(eipDefinition, secrets, new JSONObject(gatewayJson.toString()), this.context); + GatewayJson gatewayJson = new GatewayJson(context.getString(R.string.unknown_location), getObfuscationPinningIP( + + ), null, PINNED_OBFUSCATION_PROXY, capabilities); + Gateway gateway = new Gateway(eipDefinition, secrets, new JSONObject(gatewayJson.toString())); addGateway(gateway); } catch (JSONException | ConfigParser.ConfigParseError | IOException e) { e.printStackTrace(); @@ -417,7 +419,7 @@ public class GatewaysManager { for (int i = 0; i < gatewaysDefined.length(); i++) { try { JSONObject gw = gatewaysDefined.getJSONObject(i); - Gateway aux = new Gateway(eipDefinition, secrets, gw, this.context); + Gateway aux = new Gateway(eipDefinition, secrets, gw); if (gateways.get(aux.getHost()) == null) { addGateway(aux); } @@ -515,7 +517,7 @@ public class GatewaysManager { } private boolean handleGatewayPinning() { - String host = PreferenceHelper.getPinnedGateway(this.context); + String host = PreferenceHelper.getPinnedGateway(); if (host == null) { return false; } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java index d9da622c..53781f52 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VoidVpnService.java @@ -19,12 +19,10 @@ package se.leap.bitmaskclient.eip; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_ALWAYS_ON_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START_BLOCKING_VPN; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_STOP_BLOCKING_VPN; -import static se.leap.bitmaskclient.base.models.Constants.EIP_IS_ALWAYS_ON; import static se.leap.bitmaskclient.base.utils.ConfigHelper.getProviderFormattedString; import android.app.Notification; import android.content.Intent; -import android.content.SharedPreferences; import android.net.VpnService; import android.os.Binder; import android.os.Build; @@ -84,8 +82,7 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat thread = new Thread(new Runnable() { public void run() { establishBlockingVpn(); - SharedPreferences preferences = PreferenceHelper.getSharedPreferences(VoidVpnService.this.getApplicationContext()); - preferences.edit().putBoolean(EIP_IS_ALWAYS_ON, false).commit(); + PreferenceHelper.isAlwaysOnSync(false); Log.d(TAG, "start blocking vpn profile - always on = false"); } }); @@ -96,8 +93,7 @@ public class VoidVpnService extends VpnService implements Observer, VpnNotificat thread = new Thread(new Runnable() { public void run() { establishBlockingVpn(); - SharedPreferences preferences = PreferenceHelper.getSharedPreferences(VoidVpnService.this.getApplicationContext()); - preferences.edit().putBoolean(EIP_IS_ALWAYS_ON, true).commit(); + PreferenceHelper.isAlwaysOnSync(true); requestVpnWithLastSelectedProfile(); Log.d(TAG, "start blocking vpn profile - always on = true"); } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java index fa2ab352..6d5a406e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -25,7 +25,6 @@ import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS6; import static se.leap.bitmaskclient.base.models.Constants.KCP; import static se.leap.bitmaskclient.base.models.Constants.PORTS; import static se.leap.bitmaskclient.base.models.Constants.PROTOCOLS; -import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PRIVATE_KEY; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.base.models.Constants.REMOTE; import static se.leap.bitmaskclient.base.models.Constants.TCP; diff --git a/app/src/main/java/se/leap/bitmaskclient/firewall/FirewallManager.java b/app/src/main/java/se/leap/bitmaskclient/firewall/FirewallManager.java index 0e81ce30..8df1638c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/firewall/FirewallManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/firewall/FirewallManager.java @@ -92,10 +92,10 @@ public class FirewallManager implements FirewallCallback, Observer { TetheringObservable.allowVpnWifiTethering(false); TetheringObservable.allowVpnUsbTethering(false); TetheringObservable.allowVpnBluetoothTethering(false); - PreferenceHelper.allowWifiTethering(context, false); - PreferenceHelper.allowUsbTethering(context, false); - PreferenceHelper.allowBluetoothTethering(context, false); - PreferenceHelper.setUseIPv6Firewall(context, false); + PreferenceHelper.allowWifiTethering(false); + PreferenceHelper.allowUsbTethering(false); + PreferenceHelper.allowBluetoothTethering(false); + PreferenceHelper.setUseIPv6Firewall(false); } } @@ -107,7 +107,7 @@ public class FirewallManager implements FirewallCallback, Observer { public void start() { if (!isRunning) { isRunning = true; - if (PreferenceHelper.useIpv6Firewall(context)) { + if (PreferenceHelper.useIpv6Firewall()) { startIPv6Firewall(); } TetheringState tetheringState = TetheringObservable.getInstance().getTetheringState(); @@ -120,7 +120,7 @@ public class FirewallManager implements FirewallCallback, Observer { public void stop() { isRunning = false; - if (PreferenceHelper.useIpv6Firewall(context)) { + if (PreferenceHelper.useIpv6Firewall()) { stopIPv6Firewall(); } TetheringState tetheringState = TetheringObservable.getInstance().getTetheringState(); diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java index 0e7e1f4a..07ea4691 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java @@ -178,7 +178,7 @@ public class ProviderAPI extends JobIntentService implements ProviderApiManagerB private ProviderApiManager initApiManager() { OkHttpClientGenerator clientGenerator = new OkHttpClientGenerator(getResources()); - return new ProviderApiManager(PreferenceHelper.getSharedPreferences(this), getResources(), clientGenerator, this); + return new ProviderApiManager(getResources(), clientGenerator, this); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java index fdaef28b..9468f76e 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java @@ -101,7 +101,6 @@ import static se.leap.bitmaskclient.tor.TorStatusObservable.TorStatus.ON; import static se.leap.bitmaskclient.tor.TorStatusObservable.getProxyPort; import android.content.Intent; -import android.content.SharedPreferences; import android.content.res.Resources; import android.os.Bundle; import android.os.ResultReceiver; @@ -172,12 +171,10 @@ public abstract class ProviderApiManagerBase { private final ProviderApiServiceCallback serviceCallback; - protected SharedPreferences preferences; protected Resources resources; OkHttpClientGenerator clientGenerator; - ProviderApiManagerBase(SharedPreferences preferences, Resources resources, OkHttpClientGenerator clientGenerator, ProviderApiServiceCallback callback) { - this.preferences = preferences; + ProviderApiManagerBase(Resources resources, OkHttpClientGenerator clientGenerator, ProviderApiServiceCallback callback) { this.resources = resources; this.serviceCallback = callback; this.clientGenerator = clientGenerator; @@ -221,7 +218,7 @@ public abstract class ProviderApiManagerBase { } try { - if (PreferenceHelper.hasSnowflakePrefs(preferences) && !VpnStatus.isVPNActive()) { + if (PreferenceHelper.hasSnowflakePrefs() && !VpnStatus.isVPNActive()) { startTorProxy(); } } catch (InterruptedException | IllegalStateException e) { @@ -253,6 +250,7 @@ public abstract class ProviderApiManagerBase { } ProviderObservable.getInstance().setProviderForDns(null); break; + case SET_UP_PROVIDER: ProviderObservable.getInstance().setProviderForDns(provider); result = setUpProvider(provider, parameters); @@ -303,7 +301,7 @@ public abstract class ProviderApiManagerBase { if (result.getBoolean(BROADCAST_RESULT_KEY)) { Log.d(TAG, "successfully downloaded VPN certificate"); provider.setShouldUpdateVpnCertificate(false); - PreferenceHelper.storeProviderInPreferences(preferences, provider); + PreferenceHelper.storeProviderInPreferences(provider); ProviderObservable.getInstance().updateProvider(provider); } ProviderObservable.getInstance().setProviderForDns(null); @@ -315,7 +313,7 @@ public abstract class ProviderApiManagerBase { provider.setMotdJson(motd); provider.setLastMotdUpdate(System.currentTimeMillis()); } - PreferenceHelper.storeProviderInPreferences(preferences, provider); + PreferenceHelper.storeProviderInPreferences(provider); ProviderObservable.getInstance().updateProvider(provider); break; @@ -359,7 +357,7 @@ public abstract class ProviderApiManagerBase { protected boolean startTorProxy() throws InterruptedException, IllegalStateException, TimeoutException { if (EipStatus.getInstance().isDisconnected() && - PreferenceHelper.getUseSnowflake(preferences) && + PreferenceHelper.getUseSnowflake() && serviceCallback.startTorService()) { waitForTorCircuits(); if (TorStatusObservable.isCancelled()) { @@ -385,7 +383,7 @@ public abstract class ProviderApiManagerBase { void resetProviderDetails(Provider provider) { provider.reset(); - deleteProviderDetailsFromPreferences(preferences, provider.getDomain()); + deleteProviderDetailsFromPreferences(provider.getDomain()); } String formatErrorMessage(final int errorStringId) { @@ -952,7 +950,7 @@ public abstract class ProviderApiManagerBase { result = validateCertificateForProvider(result, provider); - //invalid certificate or no certificate + //invalid certificate or no certificate or unable to connect due other connectivity issues if (result.containsKey(ERRORS) || (result.containsKey(BROADCAST_RESULT_KEY) && !result.getBoolean(BROADCAST_RESULT_KEY)) ) { return result; } @@ -1037,16 +1035,16 @@ public abstract class ProviderApiManagerBase { } protected String getPersistedPrivateKey(String providerDomain) { - return getFromPersistedProvider(PROVIDER_PRIVATE_KEY, providerDomain, preferences); + return getFromPersistedProvider(PROVIDER_PRIVATE_KEY, providerDomain); } protected String getPersistedVPNCertificate(String providerDomain) { - return getFromPersistedProvider(PROVIDER_VPN_CERTIFICATE, providerDomain, preferences); + return getFromPersistedProvider(PROVIDER_VPN_CERTIFICATE, providerDomain); } protected JSONObject getPersistedProviderDefinition(String providerDomain) { try { - return new JSONObject(getFromPersistedProvider(Provider.KEY, providerDomain, preferences)); + return new JSONObject(getFromPersistedProvider(Provider.KEY, providerDomain)); } catch (JSONException e) { e.printStackTrace(); return new JSONObject(); @@ -1054,44 +1052,44 @@ public abstract class ProviderApiManagerBase { } protected String getPersistedProviderCA(String providerDomain) { - return getFromPersistedProvider(CA_CERT, providerDomain, preferences); + return getFromPersistedProvider(CA_CERT, providerDomain); } protected String getPersistedProviderApiIp(String providerDomain) { - return getFromPersistedProvider(PROVIDER_API_IP, providerDomain, preferences); + return getFromPersistedProvider(PROVIDER_API_IP, providerDomain); } protected String getPersistedProviderIp(String providerDomain) { - return getFromPersistedProvider(PROVIDER_IP, providerDomain, preferences); + return getFromPersistedProvider(PROVIDER_IP, providerDomain); } protected String getPersistedGeoIp(String providerDomain) { - return getFromPersistedProvider(GEOIP_URL, providerDomain, preferences); + return getFromPersistedProvider(GEOIP_URL, providerDomain); } protected JSONObject getPersistedMotd(String providerDomain) { try { - return new JSONObject(getFromPersistedProvider(PROVIDER_MOTD, providerDomain, preferences)); + return new JSONObject(getFromPersistedProvider(PROVIDER_MOTD, providerDomain)); } catch (JSONException e) { return new JSONObject(); } } protected long getPersistedMotdLastSeen(String providerDomain) { - return getLongFromPersistedProvider(PROVIDER_MOTD_LAST_SEEN, providerDomain, preferences); + return getLongFromPersistedProvider(PROVIDER_MOTD_LAST_SEEN, providerDomain); } protected long getPersistedMotdLastUpdate(String providerDomain) { - return getLongFromPersistedProvider(PROVIDER_MOTD_LAST_UPDATED, providerDomain, preferences); + return getLongFromPersistedProvider(PROVIDER_MOTD_LAST_UPDATED, providerDomain); } protected Set<String> getPersistedMotdHashes(String providerDomain) { - return getStringSetFromPersistedProvider(PROVIDER_MOTD_HASHES, providerDomain, preferences); + return getStringSetFromPersistedProvider(PROVIDER_MOTD_HASHES, providerDomain); } protected boolean hasUpdatedProviderDetails(String domain) { - return preferences.contains(Provider.KEY + "." + domain) && preferences.contains(CA_CERT + "." + domain); + return PreferenceHelper.hasKey(Provider.KEY + "." + domain) && PreferenceHelper.hasKey(CA_CERT + "." + domain); } /** diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java index 775e174a..0f6c5090 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java @@ -41,7 +41,7 @@ import se.leap.bitmaskclient.base.models.Provider; */ public class ProviderManager implements AdapteeCollection<Provider> { - private AssetManager assetsManager; + private final AssetManager assetsManager; private File externalFilesDir; private Set<Provider> defaultProviders; private Set<Provider> customProviders; @@ -49,6 +49,7 @@ public class ProviderManager implements AdapteeCollection<Provider> { private Set<String> customProviderURLs; private static ProviderManager instance; + private boolean addDummyEntry = false; public static ProviderManager getInstance(AssetManager assetsManager, File externalFilesDir) { if (instance == null) @@ -62,6 +63,10 @@ public class ProviderManager implements AdapteeCollection<Provider> { instance = null; } + public void setAddDummyEntry(boolean addDummyEntry) { + this.addDummyEntry = addDummyEntry; + } + private ProviderManager(AssetManager assetManager, File externalFilesDir) { this.assetsManager = assetManager; addDefaultProviders(assetManager); @@ -145,13 +150,18 @@ public class ProviderManager implements AdapteeCollection<Provider> { } public List<Provider> providers() { + return providers(addDummyEntry); + } + + private List<Provider> providers(boolean addEmptyProvider) { List<Provider> allProviders = new ArrayList<>(); allProviders.addAll(defaultProviders); if(customProviders != null) allProviders.addAll(customProviders); - //add an option to add a custom provider - //TODO: refactor me? - allProviders.add(new Provider()); + if (addEmptyProvider) { + //add an option to add a custom provider + allProviders.add(new Provider()); + } return allProviders; } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupFailedDialog.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupFailedDialog.java index 0d5f903f..338a60e9 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupFailedDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupFailedDialog.java @@ -143,7 +143,7 @@ public class ProviderSetupFailedDialog extends DialogFragment { handleTorTimeoutError(); }); builder.setNeutralButton(R.string.retry_unobfuscated, ((dialog, id) -> { - PreferenceHelper.useSnowflake(getContext(), false); + PreferenceHelper.useSnowflake(false); handleTorTimeoutError(); })); default: @@ -189,7 +189,7 @@ public class ProviderSetupFailedDialog extends DialogFragment { interfaceWithConfigurationWizard = (DownloadFailedDialogInterface) context; } catch (ClassCastException e) { throw new ClassCastException(context.toString() - + " must implement NoticeDialogListener"); + + " must implement DownloadFailedDialogInterface"); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/SetupViewPagerAdapter.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/SetupViewPagerAdapter.java new file mode 100644 index 00000000..39122572 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/SetupViewPagerAdapter.java @@ -0,0 +1,62 @@ +package se.leap.bitmaskclient.providersetup; + +import static se.leap.bitmaskclient.providersetup.fragments.SetupFragmentFactory.*; +import static se.leap.bitmaskclient.providersetup.fragments.SetupFragmentFactory.CIRCUMVENTION_SETUP_FRAGMENT; +import static se.leap.bitmaskclient.providersetup.fragments.SetupFragmentFactory.CONFIGURE_PROVIDER_FRAGMENT; +import static se.leap.bitmaskclient.providersetup.fragments.SetupFragmentFactory.PROVIDER_SELECTION_FRAGMENT; + +import android.content.Intent; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.lifecycle.Lifecycle; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import java.util.ArrayList; + +import se.leap.bitmaskclient.providersetup.fragments.SetupFragmentFactory; + +public class SetupViewPagerAdapter extends FragmentStateAdapter { + + private SetupFragmentFactory setupFragmentFactory; + + private SetupViewPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) { + super(fragmentManager, lifecycle); + } + + public SetupViewPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle, Intent vpnPermissionRequest, Boolean showNotificationPermission) { + this(fragmentManager, lifecycle); + ArrayList<Integer> fragments = new ArrayList<>(); + fragments.add(PROVIDER_SELECTION_FRAGMENT); + fragments.add(CIRCUMVENTION_SETUP_FRAGMENT); + fragments.add(CONFIGURE_PROVIDER_FRAGMENT); + if (vpnPermissionRequest != null) { + fragments.add(VPN_PERMISSON_EDUCATIONAL_FRAGMENT); + fragments.add(VPN_PERMISSON_FRAGMENT); + } + if (showNotificationPermission) { + fragments.add(NOTIFICATION_PERMISSON_EDUCATIONAL_FRAGMENT); + fragments.add(NOTIFICATION_PERMISSON_FRAGMENT); + } + fragments.add(SUCCESS_FRAGMENT); + setupFragmentFactory = new SetupFragmentFactory(fragments, vpnPermissionRequest); + } + + @NonNull + @Override + public Fragment createFragment(int position) { + return setupFragmentFactory.createFragment(position); + } + + @Override + public int getItemCount() { + return setupFragmentFactory.getItemCount(); + } + + public int getFragmentPostion(int fragmentType) { + return setupFragmentFactory.getPos(fragmentType); + } + + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/TorLogAdapter.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/TorLogAdapter.java new file mode 100644 index 00000000..3df0fd94 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/TorLogAdapter.java @@ -0,0 +1,61 @@ +package se.leap.bitmaskclient.providersetup; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.appcompat.widget.AppCompatTextView; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.providersetup.activities.ConfigWizardBaseActivity; + +public class TorLogAdapter extends RecyclerView.Adapter<TorLogAdapter.ViewHolder> { + private List<String> values; + public boolean postponeUpdate; + + static class ViewHolder extends RecyclerView.ViewHolder { + public AppCompatTextView logTextLabel; + public View layout; + + public ViewHolder(View v) { + super(v); + layout = v; + logTextLabel = v.findViewById(android.R.id.text1); + } + } + + public void updateData(List<String> data) { + values = data; + if (!postponeUpdate) { + notifyDataSetChanged(); + } + } + + public TorLogAdapter(List<String> data) { + values = data; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + LayoutInflater inflater = LayoutInflater.from( + parent.getContext()); + View v = inflater.inflate(R.layout.v_log_item, parent, false); + return new ViewHolder(v); + } + + @Override + public void onBindViewHolder(ViewHolder holder, final int position) { + final String log = values.get(position); + holder.logTextLabel.setText(log); + } + + @Override + public int getItemCount() { + return values.size(); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/CancelCallback.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/CancelCallback.java new file mode 100644 index 00000000..a3f387d8 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/CancelCallback.java @@ -0,0 +1,5 @@ +package se.leap.bitmaskclient.providersetup.activities; + +public interface CancelCallback { + void onCanceled(); +} diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ConfigWizardBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ConfigWizardBaseActivity.java index caba1436..1bf66d7d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ConfigWizardBaseActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ConfigWizardBaseActivity.java @@ -11,11 +11,9 @@ import static se.leap.bitmaskclient.tor.TorStatusObservable.getLastSnowflakeLog; import static se.leap.bitmaskclient.tor.TorStatusObservable.getLastTorLog; import static se.leap.bitmaskclient.tor.TorStatusObservable.getStringForCurrentStatus; -import android.content.SharedPreferences; import android.graphics.Rect; import android.os.Build; import android.os.Bundle; -import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; @@ -40,8 +38,8 @@ import java.util.Observer; import butterknife.BindView; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.models.Provider; -import se.leap.bitmaskclient.base.utils.PreferenceHelper; import se.leap.bitmaskclient.base.views.ProviderHeaderView; +import se.leap.bitmaskclient.providersetup.TorLogAdapter; import se.leap.bitmaskclient.tor.TorStatusObservable; /** @@ -54,7 +52,6 @@ public abstract class ConfigWizardBaseActivity extends ButterKnifeActivity imple private static final String TAG = ConfigWizardBaseActivity.class.getName(); public static final float GUIDE_LINE_COMPACT_DELTA = 0.1f; - protected SharedPreferences preferences; @BindView(R.id.header) ProviderHeaderView providerHeaderView; @@ -133,7 +130,6 @@ public abstract class ConfigWizardBaseActivity extends ButterKnifeActivity imple @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - preferences = PreferenceHelper.getSharedPreferences(this); provider = getIntent().getParcelableExtra(PROVIDER_KEY); } @@ -431,50 +427,4 @@ public abstract class ConfigWizardBaseActivity extends ButterKnifeActivity imple snowflakeState.setText(snowflakeLog); } - static class TorLogAdapter extends RecyclerView.Adapter<TorLogAdapter.ViewHolder> { - private List<String> values; - private boolean postponeUpdate; - - static class ViewHolder extends RecyclerView.ViewHolder { - public AppCompatTextView logTextLabel; - public View layout; - - public ViewHolder(View v) { - super(v); - layout = v; - logTextLabel = v.findViewById(android.R.id.text1); - } - } - - public void updateData(List<String> data) { - values = data; - if (!postponeUpdate) { - notifyDataSetChanged(); - } - } - - public TorLogAdapter(List<String> data) { - values = data; - } - - @NonNull - @Override - public TorLogAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - LayoutInflater inflater = LayoutInflater.from( - parent.getContext()); - View v = inflater.inflate(R.layout.v_log_item, parent, false); - return new TorLogAdapter.ViewHolder(v); - } - - @Override - public void onBindViewHolder(TorLogAdapter.ViewHolder holder, final int position) { - final String log = values.get(position); - holder.logTextLabel.setText(log); - } - - @Override - public int getItemCount() { - return values.size(); - } - } } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivity.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivity.java new file mode 100644 index 00000000..27ca6658 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivity.java @@ -0,0 +1,328 @@ +package se.leap.bitmaskclient.providersetup.activities; + +import static android.view.View.GONE; +import static android.view.View.VISIBLE; +import static androidx.appcompat.app.ActionBar.DISPLAY_SHOW_CUSTOM; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.deleteProviderDetailsFromPreferences; +import static se.leap.bitmaskclient.providersetup.fragments.SetupFragmentFactory.CONFIGURE_PROVIDER_FRAGMENT; +import static se.leap.bitmaskclient.tor.TorStatusObservable.TorStatus.OFF; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.net.VpnService; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; +import android.view.Gravity; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; +import androidx.core.content.res.ResourcesCompat; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentTransaction; +import androidx.viewpager2.widget.ViewPager2; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashSet; + +import se.leap.bitmaskclient.BuildConfig; +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.base.FragmentManagerEnhanced; +import se.leap.bitmaskclient.base.models.Provider; +import se.leap.bitmaskclient.base.models.ProviderObservable; +import se.leap.bitmaskclient.base.utils.ViewHelper; +import se.leap.bitmaskclient.base.views.ActionBarTitle; +import se.leap.bitmaskclient.databinding.ActivitySetupBinding; +import se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog; +import se.leap.bitmaskclient.providersetup.SetupViewPagerAdapter; +import se.leap.bitmaskclient.tor.TorServiceCommand; +import se.leap.bitmaskclient.tor.TorStatusObservable; + +public class SetupActivity extends AppCompatActivity implements SetupActivityCallback, ProviderSetupFailedDialog.DownloadFailedDialogInterface { + + public static final String EXTRA_PROVIDER = "EXTRA_PROVIDER"; + public static final String EXTRA_CURRENT_POSITION = "EXTRA_CURRENT_POSITION"; + private static final String TAG = SetupActivity.class.getSimpleName(); + ActivitySetupBinding binding; + Provider provider; + private int currentPosition = 0; + + private final HashSet<CancelCallback> cancelCallbacks = new HashSet<>(); + private FragmentManagerEnhanced fragmentManager; + SetupViewPagerAdapter adapter; + + @SuppressLint("ClickableViewAccessibility") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState != null) { + provider = savedInstanceState.getParcelable(EXTRA_PROVIDER); + currentPosition = savedInstanceState.getInt(EXTRA_CURRENT_POSITION); + } + + binding = ActivitySetupBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + fragmentManager = new FragmentManagerEnhanced(getSupportFragmentManager()); + ArrayList<View> indicatorViews = new ArrayList<>(); + + for (int i = 0; i < 4; i++) { + addIndicatorView(indicatorViews); + } + + Intent requestVpnPermission = VpnService.prepare(this); + if (requestVpnPermission != null) { + addIndicatorView(indicatorViews); + addIndicatorView(indicatorViews); + } + + boolean showNotificationPermissionFragments = false; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + showNotificationPermissionFragments = shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS); + if (showNotificationPermissionFragments) { + addIndicatorView(indicatorViews); + addIndicatorView(indicatorViews); + } + } + } + + adapter = new SetupViewPagerAdapter(getSupportFragmentManager(), getLifecycle(), requestVpnPermission, showNotificationPermissionFragments); + + binding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + currentPosition = position; + for (int i = 0; i < indicatorViews.size(); i++) { + ((ViewGroup) indicatorViews.get(i)). + getChildAt(0). + setBackgroundColor(ContextCompat.getColor(SetupActivity.this, (i == position) ? R.color.colorPrimaryDark : R.color.colorDisabled)); + } + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(position == 0 && ProviderObservable.getInstance().getCurrentProvider().isConfigured()); + } + } + }); + binding.viewPager.setAdapter(adapter); + binding.viewPager.setUserInputEnabled(false); + binding.viewPager.setCurrentItem(currentPosition, false); + + binding.setupNextButton.setOnClickListener(v -> { + int currentPos = binding.viewPager.getCurrentItem(); + int newPos = currentPos + 1; + if (newPos >= binding.viewPager.getAdapter().getItemCount()) { + Toast.makeText(SetupActivity.this, "SetupFinished \\o/", Toast.LENGTH_LONG).show(); + return; + } + binding.viewPager.setCurrentItem(newPos); + }); + binding.setupCancelButton.setOnClickListener(v -> { + cancel(); + }); + setupActionBar(); + } + + @Override + protected void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + if (provider != null) { + outState.putParcelable(EXTRA_PROVIDER, provider); + outState.putInt(EXTRA_CURRENT_POSITION, currentPosition); + } + } + + private void cancel() { + binding.viewPager.setCurrentItem(0, false); + if (TorStatusObservable.getStatus() != OFF) { + Log.d(TAG, "SHUTDOWN - cancelSettingUpProvider"); + TorServiceCommand.stopTorServiceAsync(this); + } + provider = null; + for (CancelCallback cancelCallback : cancelCallbacks) { + cancelCallback.onCanceled(); + } + } + + private void addIndicatorView(ArrayList<View> indicatorViews) { + // FIXME: we have to work around a bug in our usage of CardView, that + // doesn't let us programmatically add new indicator views as needed. + // for some reason the cardBackgroundColor property is ignored if we add + // the card view dynamically + View v = binding.indicatorContainer.getChildAt(indicatorViews.size()); + if (v == null) { + throw new IllegalStateException("Too few indicator views in layout hard-coded"); + } + v.setVisibility(VISIBLE); + indicatorViews.add(v); + } + + private void setupActionBar() { + setSupportActionBar(binding.toolbar); + final ActionBar actionBar = getSupportActionBar(); + Context context = actionBar.getThemedContext(); + actionBar.setDisplayOptions(DISPLAY_SHOW_CUSTOM); + + ActionBarTitle actionBarTitle = new ActionBarTitle(context); + actionBarTitle.setTitleCaps(BuildConfig.actionbar_capitalize_title); + actionBarTitle.setTitle(getString(R.string.app_name)); + + final Drawable upArrow = ResourcesCompat.getDrawable(getResources(), R.drawable.ic_back, getTheme()); + actionBar.setHomeAsUpIndicator(upArrow); + + actionBar.setDisplayHomeAsUpEnabled(currentPosition == 0 && ProviderObservable.getInstance().getCurrentProvider().isConfigured()); + ViewHelper.setActivityBarColor(this, R.color.bg_setup_status_bar, R.color.bg_setup_action_bar, R.color.colorActionBarTitleFont); + @ColorInt int titleColor = ContextCompat.getColor(context, R.color.colorActionBarTitleFont); + actionBarTitle.setTitleTextColor(titleColor); + + actionBarTitle.setCentered(BuildConfig.actionbar_center_title); + actionBarTitle.setSingleBoldTitle(); + if (BuildConfig.actionbar_center_title) { + ActionBar.LayoutParams params = new ActionBar.LayoutParams( + ActionBar.LayoutParams.WRAP_CONTENT, + ActionBar.LayoutParams.MATCH_PARENT, + Gravity.CENTER); + actionBar.setCustomView(actionBarTitle, params); + } else { + actionBar.setCustomView(actionBarTitle); + } + } + + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onSetupStepValidationChanged(boolean isValid) { + binding.setupNextButton.setEnabled(isValid); + } + + @Override + public void registerOnPageChangeCallback(ViewPager2.OnPageChangeCallback callback) { + binding.viewPager.registerOnPageChangeCallback(callback); + } + + @Override + public void removeOnPageChangeCallback(ViewPager2.OnPageChangeCallback callback) { + binding.viewPager.unregisterOnPageChangeCallback(callback); + } + + @Override + public void registerCancelCallback(CancelCallback cancelCallback) { + cancelCallbacks.add(cancelCallback); + } + + @Override + public void removeCancelCallback(CancelCallback cancelCallback) { + cancelCallbacks.remove(cancelCallback); + } + + @Override + public void setNavigationButtonHidden(boolean isHidden) { + binding.setupNextButton.setVisibility(isHidden ? GONE : VISIBLE); + } + + @Override + public void setCancelButtonHidden(boolean isHidden) { + binding.setupCancelButton.setVisibility(isHidden ? GONE : VISIBLE); + } + + @Override + public void onProviderSelected(Provider provider) { + this.provider = provider; + } + + @Override + public void onConfigurationSuccess() { + binding.viewPager.setCurrentItem(binding.viewPager.getCurrentItem() + 1); + } + + @Override + public Provider getSelectedProvider() { + return provider; + } + + @Override + public int getCurrentPosition() { + return currentPosition; + } + + @Override + public void onSetupFinished() { + Intent intent = getIntent(); + intent.putExtra(Provider.KEY, provider); + setResult(RESULT_OK, intent); + finish(); + } + + @Override + public void onError(String reasonToFail) { + binding.viewPager.setCurrentItem(0, false); + try { + FragmentTransaction fragmentTransaction = fragmentManager.removePreviousFragment(ProviderSetupFailedDialog.TAG); + DialogFragment newFragment; + try { + JSONObject errorJson = new JSONObject(reasonToFail); + newFragment = ProviderSetupFailedDialog.newInstance(provider, errorJson, false); + } catch (JSONException e) { + e.printStackTrace(); + newFragment = ProviderSetupFailedDialog.newInstance(provider, reasonToFail); + } catch (NullPointerException e) { + //reasonToFail was null + return; + } + newFragment.show(fragmentTransaction, ProviderSetupFailedDialog.TAG); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + + @Override + public void retrySetUpProvider(@NonNull Provider provider) { + onProviderSelected(provider); + binding.viewPager.setCurrentItem(adapter.getFragmentPostion(CONFIGURE_PROVIDER_FRAGMENT)); + } + + @Override + public void cancelSettingUpProvider() { + cancel(); + } + + @Override + public void updateProviderDetails() { + provider.reset(); + deleteProviderDetailsFromPreferences(provider.getDomain()); + binding.viewPager.setCurrentItem(adapter.getFragmentPostion(CONFIGURE_PROVIDER_FRAGMENT)); + } + + @Override + public void addAndSelectNewProvider(String url) { + // ignore, not implemented anymore in new setup flow + } + + @Override + protected void onDestroy() { + super.onDestroy(); + adapter = null; + } +}
\ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivityCallback.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivityCallback.java new file mode 100644 index 00000000..2c77dfd0 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivityCallback.java @@ -0,0 +1,33 @@ +package se.leap.bitmaskclient.providersetup.activities; + +import androidx.viewpager2.widget.ViewPager2; + +import se.leap.bitmaskclient.base.models.Provider; + +public interface SetupActivityCallback { + + void onSetupStepValidationChanged(boolean isValid); + void registerOnPageChangeCallback(ViewPager2.OnPageChangeCallback callback); + void removeOnPageChangeCallback(ViewPager2.OnPageChangeCallback callback); + + void registerCancelCallback(CancelCallback cancelCallback); + + void removeCancelCallback(CancelCallback cancelCallback); + + void setNavigationButtonHidden(boolean isHidden); + + void setCancelButtonHidden(boolean isHidden); + + void onProviderSelected(Provider provider); + + void onConfigurationSuccess(); + + Provider getSelectedProvider(); + + int getCurrentPosition(); + + void onSetupFinished(); + + void onError(String reasonToFail); +} + diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/DnsResolver.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/DnsResolver.java index e8249692..30d008d5 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/DnsResolver.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/DnsResolver.java @@ -19,6 +19,7 @@ import okhttp3.dnsoverhttps.DnsOverHttps; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.models.ProviderObservable; import se.leap.bitmaskclient.base.utils.IPAddress; +import se.leap.bitmaskclient.tor.TorStatusObservable; public class DnsResolver implements Dns { OkHttpClient dohHttpClient; @@ -34,7 +35,7 @@ public class DnsResolver implements Dns { public List<InetAddress> lookup(@NonNull String hostname) throws UnknownHostException { Log.d("DNS", "trying to resolve DNS for " + hostname); List<InetAddress> list = null; - if (preferDoH) { + if (preferDoH && !"127.0.0.1".equals(hostname)) { if ((list = tryLookupDoH(hostname)) == null) { list = tryLookupSystemDNS(hostname); } @@ -71,7 +72,7 @@ public class DnsResolver implements Dns { private List<InetAddress> tryLookupSystemDNS(@NonNull String hostname) throws RuntimeException, UnknownHostException { try { - Log.d("DNS", "trying to resolve " + hostname + "with system DNS"); + Log.d("DNS", "trying to resolve " + hostname + " with system DNS"); return Dns.SYSTEM.lookup(hostname); } catch (UnknownHostException e) { e.printStackTrace(); diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/OkHttpClientGenerator.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/OkHttpClientGenerator.java index 97393551..b0dbd49b 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/OkHttpClientGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/connectivity/OkHttpClientGenerator.java @@ -43,10 +43,12 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.cert.CertificateException; +import java.time.Duration; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.concurrent.TimeUnit; import okhttp3.CipherSuite; import okhttp3.ConnectionSpec; @@ -139,6 +141,8 @@ public class OkHttpClientGenerator { clientBuilder.dns(new DnsResolver(clientBuilder.build(), true)); sslCompatFactory.initSSLSocketFactory(clientBuilder); + clientBuilder.connectTimeout(45L, TimeUnit.SECONDS); + clientBuilder.readTimeout(45L, TimeUnit.SECONDS); return clientBuilder.build(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java new file mode 100644 index 00000000..151361ba --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java @@ -0,0 +1,96 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import android.content.Context; +import android.os.Bundle; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.viewpager2.widget.ViewPager2; + +import se.leap.bitmaskclient.providersetup.activities.SetupActivityCallback; + +public class BaseSetupFragment extends Fragment { + + public static String EXTRA_POSITION = "EXTRA_POSITION"; + private boolean callFragmentSelected = false; + SetupActivityCallback setupActivityCallback; + + private final ViewPager2.OnPageChangeCallback viewPagerCallback = new ViewPager2.OnPageChangeCallback() { + @Override + public void onPageSelected(int position) { + super.onPageSelected(position); + if (position == BaseSetupFragment.this.position) { + handleCallFragmentSelected(); + } else { + callFragmentSelected = false; + } + } + }; + private int position; + + public static Bundle initBundle(int pos) { + Bundle bundle = new Bundle(); + bundle.putInt(EXTRA_POSITION, pos); + return bundle; + } + + public BaseSetupFragment() { + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.position = getArguments().getInt(EXTRA_POSITION); + } + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + if (getActivity() instanceof SetupActivityCallback) { + setupActivityCallback = (SetupActivityCallback) getActivity(); + } else { + throw new IllegalStateException("These setup fragments are closely coupled to SetupActivityCallback interface. Activities instantiating them are required to implement the interface"); + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putInt(EXTRA_POSITION, position); + super.onSaveInstanceState(outState); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setupActivityCallback.registerOnPageChangeCallback(viewPagerCallback); + if (setupActivityCallback.getCurrentPosition() == position) { + handleCallFragmentSelected(); + } + } + + private void handleCallFragmentSelected() { + if (!callFragmentSelected) { + callFragmentSelected = true; + onFragmentSelected(); + } + } + + @Override + public void onDestroyView() { + setupActivityCallback.removeOnPageChangeCallback(viewPagerCallback); + callFragmentSelected = false; + super.onDestroyView(); + } + + @Override + public void onDetach() { + setupActivityCallback = null; + super.onDetach(); + } + + public void onFragmentSelected() { + + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java new file mode 100644 index 00000000..11fa582b --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java @@ -0,0 +1,58 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import android.graphics.Typeface; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import se.leap.bitmaskclient.base.utils.PreferenceHelper; +import se.leap.bitmaskclient.databinding.FCircumventionSetupBinding; + +public class CircumventionSetupFragment extends BaseSetupFragment { + + public static CircumventionSetupFragment newInstance(int position) { + CircumventionSetupFragment fragment = new CircumventionSetupFragment(); + fragment.setArguments(initBundle(position)); + return fragment; + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + FCircumventionSetupBinding binding = FCircumventionSetupBinding.inflate(inflater, container, false); + + binding.circumventionRadioGroup.setOnCheckedChangeListener((group, checkedId) -> { + if (binding.rbCircumvention.getId() == checkedId) { + PreferenceHelper.useBridges(true); + PreferenceHelper.useSnowflake(true); + binding.tvCircumventionDetailDescription.setVisibility(View.VISIBLE); + binding.rbCircumvention.setTypeface(Typeface.DEFAULT, Typeface.BOLD); + binding.rbPlainVpn.setTypeface(Typeface.DEFAULT, Typeface.NORMAL); + return; + } + + PreferenceHelper.useBridges(false); + PreferenceHelper.useSnowflake(false); + binding.tvCircumventionDetailDescription.setVisibility(View.GONE); + binding.rbPlainVpn.setTypeface(Typeface.DEFAULT, Typeface.BOLD); + binding.rbCircumvention.setTypeface(Typeface.DEFAULT, Typeface.NORMAL); + }); + + int id = (PreferenceHelper.hasSnowflakePrefs() && PreferenceHelper.getUseSnowflake()) ? + binding.rbCircumvention.getId() : + binding.rbPlainVpn.getId(); + binding.circumventionRadioGroup.check(id); + return binding.getRoot(); + } + + @Override + public void onFragmentSelected() { + super.onFragmentSelected(); + setupActivityCallback.setCancelButtonHidden(false); + setupActivityCallback.setNavigationButtonHidden(false); + } +}
\ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java new file mode 100644 index 00000000..3c36065e --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java @@ -0,0 +1,222 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import static android.app.Activity.RESULT_CANCELED; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; +import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE; +import static se.leap.bitmaskclient.R.string.description_configure_provider; +import static se.leap.bitmaskclient.R.string.description_configure_provider_circumvention; +import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE; +import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUseSnowflake; +import static se.leap.bitmaskclient.base.utils.ViewHelper.animateContainerVisibility; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.MISSING_NETWORK_CONNECTION; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_NOK; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_OK; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.SET_UP_PROVIDER; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_EXCEPTION; +import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_TIMEOUT; +import static se.leap.bitmaskclient.tor.TorStatusObservable.getBootstrapProgress; +import static se.leap.bitmaskclient.tor.TorStatusObservable.getLastLogs; +import static se.leap.bitmaskclient.tor.TorStatusObservable.getLastSnowflakeLog; +import static se.leap.bitmaskclient.tor.TorStatusObservable.getLastTorLog; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; +import java.util.Observable; +import java.util.Observer; + +import se.leap.bitmaskclient.base.models.Provider; +import se.leap.bitmaskclient.databinding.FConfigureProviderBinding; +import se.leap.bitmaskclient.eip.EipSetupListener; +import se.leap.bitmaskclient.eip.EipSetupObserver; +import se.leap.bitmaskclient.providersetup.ProviderAPICommand; +import se.leap.bitmaskclient.providersetup.TorLogAdapter; +import se.leap.bitmaskclient.providersetup.activities.CancelCallback; +import se.leap.bitmaskclient.tor.TorStatusObservable; + +public class ConfigureProviderFragment extends BaseSetupFragment implements Observer, CancelCallback, EipSetupListener { + + private static final String TAG = ConfigureProviderFragment.class.getSimpleName(); + + FConfigureProviderBinding binding; + private boolean isExpanded = false; + private boolean ignoreProviderAPIUpdates = false; + private TorLogAdapter torLogAdapter; + + + public static ConfigureProviderFragment newInstance(int position) { + ConfigureProviderFragment fragment = new ConfigureProviderFragment(); + fragment.setArguments(initBundle(position)); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + torLogAdapter = new TorLogAdapter(getLastLogs()); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + binding = FConfigureProviderBinding.inflate(inflater, container, false); + binding.detailContainer.setVisibility(getUseSnowflake() ? VISIBLE : GONE); + binding.tvCircumventionDescription.setText(getUseSnowflake() ? description_configure_provider_circumvention : description_configure_provider); + binding.detailHeaderContainer.setOnClickListener(v -> { + binding.ivExpand.animate().setDuration(250).rotation(isExpanded ? -90 : 0); + showConnectionDetails(); + animateContainerVisibility(binding.expandableDetailContainer, isExpanded); + isExpanded = !isExpanded; + }); + + binding.ivExpand.animate().setDuration(0).rotation(270); + LinearLayoutManager layoutManager = new LinearLayoutManager(this.getContext()); + binding.connectionDetailLogs.setLayoutManager(layoutManager); + binding.connectionDetailLogs.setAdapter(torLogAdapter); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setupActivityCallback.registerCancelCallback(this); + TorStatusObservable.getInstance().addObserver(this); + EipSetupObserver.addListener(this); + } + + @Override + public void onDestroyView() { + setupActivityCallback.removeCancelCallback(this); + TorStatusObservable.getInstance().deleteObserver(this); + EipSetupObserver.removeListener(this); + binding = null; + super.onDestroyView(); + } + + @Override + public void onFragmentSelected() { + super.onFragmentSelected(); + ignoreProviderAPIUpdates = false; + binding.detailContainer.setVisibility(getUseSnowflake() ? VISIBLE : GONE); + binding.tvCircumventionDescription.setText(getUseSnowflake() ? description_configure_provider_circumvention : description_configure_provider); + setupActivityCallback.setNavigationButtonHidden(true); + setupActivityCallback.setCancelButtonHidden(false); + ProviderAPICommand.execute(getContext(), SET_UP_PROVIDER, setupActivityCallback.getSelectedProvider()); + } + + protected void showConnectionDetails() { + + binding.connectionDetailLogs.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + if (newState != SCROLL_STATE_IDLE) { + torLogAdapter.postponeUpdate = true; + } else if (newState == SCROLL_STATE_IDLE && getFirstVisibleItemPosion() == 0) { + torLogAdapter.postponeUpdate = false; + } + } + }); + + binding.snowflakeState.setText(getLastSnowflakeLog()); + binding.torState.setText(getLastTorLog()); + } + + private int getFirstVisibleItemPosion() { + return ((LinearLayoutManager) binding.connectionDetailLogs.getLayoutManager()).findFirstVisibleItemPosition(); + } + + @Override + public void update(Observable o, Object arg) { + if (o instanceof TorStatusObservable) { + Activity activity = getActivity(); + if (activity == null || binding == null) { + return; + } + activity.runOnUiThread(() -> { + if (binding == null) { + return; + } + if (TorStatusObservable.getStatus() != TorStatusObservable.TorStatus.OFF) { + if (binding.connectionDetailContainer.getVisibility() == GONE) { + showConnectionDetails(); + } else { + setLogs(getLastTorLog(), getLastSnowflakeLog(), getLastLogs()); + } + } + binding.tvProgressStatus.setText(TorStatusObservable.getStringForCurrentStatus(activity)); + binding.progressSpinner.update(getBootstrapProgress()); + }); + } + } + + protected void setLogs(String torLog, String snowflakeLog, List<String> lastLogs) { + torLogAdapter.updateData(lastLogs); + binding.torState.setText(torLog); + binding.snowflakeState.setText(snowflakeLog); + } + + @Override + public void onCanceled() { + ignoreProviderAPIUpdates = true; + } + + @Override + public void handleEipEvent(Intent intent) {} + + @Override + public void handleProviderApiEvent(Intent intent) { + int resultCode = intent.getIntExtra(BROADCAST_RESULT_CODE, RESULT_CANCELED); + Bundle resultData = intent.getParcelableExtra(BROADCAST_RESULT_KEY); + if (resultData == null) { + resultData = Bundle.EMPTY; + } + Provider provider = resultData.getParcelable(PROVIDER_KEY); + if (ignoreProviderAPIUpdates || + provider == null || + !setupActivityCallback.getSelectedProvider().getDomain().equals(provider.getDomain())) { + return; + } + + switch (resultCode) { + case PROVIDER_OK: + if (provider.allowsAnonymous()) { + ProviderAPICommand.execute(this.getContext(), DOWNLOAD_VPN_CERTIFICATE, provider); + } else { + // TODO: implement error message that this client only supports anonymous usage + } + break; + case CORRECTLY_DOWNLOADED_VPN_CERTIFICATE: + setupActivityCallback.onProviderSelected(provider); + setupActivityCallback.onConfigurationSuccess(); + break; + case PROVIDER_NOK: + case INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE: + case MISSING_NETWORK_CONNECTION: + case TOR_EXCEPTION: + case TOR_TIMEOUT: + String reasonToFail = resultData.getString(ERRORS); + setupActivityCallback.onError(reasonToFail); + break; + + } + } + +}
\ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java new file mode 100644 index 00000000..4226d804 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java @@ -0,0 +1,117 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Toast; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import se.leap.bitmaskclient.databinding.FEmptyPermissionSetupBinding; + +public class EmptyPermissionSetupFragment extends BaseSetupFragment { + + public static String EXTRA_VPN_INTENT = "EXTRA_VPN_INTENT"; + public static String EXTRA_NOTIFICATION_PERMISSON_ACTION = "EXTRA_NOTIFICATION_PERMISSON_ACTION"; + + private String notificationPermissionAction = null; + private Intent vpnPermissionIntent = null; + + private final ActivityResultLauncher<String> requestNotificationPermissionLauncher = + registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { + if (isGranted) { + if (setupActivityCallback != null) { + setupActivityCallback.onConfigurationSuccess(); + } + } else { + Toast.makeText(getContext(), "Permission request failed :(", Toast.LENGTH_LONG).show(); + setupActivityCallback.setNavigationButtonHidden(false); + // TODO: implement sth. useful + } + }); + + private final ActivityResultLauncher<Intent> requestVpnPermissionLauncher = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK) { + if (setupActivityCallback != null) { + setupActivityCallback.onConfigurationSuccess(); + } + } else { + Toast.makeText(getContext(), "Permission request failed :(", Toast.LENGTH_LONG).show(); + setupActivityCallback.setNavigationButtonHidden(false); + // TODO: implement sth. useful + } + } + ); + + + public static EmptyPermissionSetupFragment newInstance(int position, Intent vpnPermissionIntent) { + Bundle bundle = initBundle(position); + bundle.putParcelable(EXTRA_VPN_INTENT, vpnPermissionIntent); + EmptyPermissionSetupFragment fragment = new EmptyPermissionSetupFragment(); + fragment.setArguments(bundle); + return fragment; + } + + public static EmptyPermissionSetupFragment newInstance(int position, String notificationPermissionAction) { + Bundle bundle = initBundle(position); + bundle.putString(EXTRA_NOTIFICATION_PERMISSON_ACTION, notificationPermissionAction); + EmptyPermissionSetupFragment fragment = new EmptyPermissionSetupFragment(); + fragment.setArguments(bundle); + return fragment; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.vpnPermissionIntent = getArguments().getParcelable(EXTRA_VPN_INTENT); + this.notificationPermissionAction = getArguments().getString(EXTRA_NOTIFICATION_PERMISSON_ACTION); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + FEmptyPermissionSetupBinding binding = FEmptyPermissionSetupBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + if (vpnPermissionIntent != null) { + outState.putParcelable(EXTRA_VPN_INTENT, vpnPermissionIntent); + } + if (notificationPermissionAction != null) { + outState.putString(EXTRA_NOTIFICATION_PERMISSON_ACTION, notificationPermissionAction); + } + super.onSaveInstanceState(outState); + } + + @Override + public void onViewStateRestored(@Nullable Bundle savedInstanceState) { + super.onViewStateRestored(savedInstanceState); + } + + @Override + public void onFragmentSelected() { + super.onFragmentSelected(); + if (notificationPermissionAction != null) { + requestNotificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); + } else if (vpnPermissionIntent != null) { + requestVpnPermissionLauncher.launch(vpnPermissionIntent); + } + + setupActivityCallback.setNavigationButtonHidden(true); + setupActivityCallback.setCancelButtonHidden(true); + } + + +}
\ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/NotificationSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/NotificationSetupFragment.java new file mode 100644 index 00000000..a9589336 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/NotificationSetupFragment.java @@ -0,0 +1,35 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import se.leap.bitmaskclient.databinding.FNotificationSetupBinding; + +public class NotificationSetupFragment extends BaseSetupFragment { + + public static NotificationSetupFragment newInstance(int position) { + NotificationSetupFragment fragment = new NotificationSetupFragment(); + fragment.setArguments(initBundle(position)); + return fragment; + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + FNotificationSetupBinding binding = FNotificationSetupBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onFragmentSelected() { + super.onFragmentSelected(); + setupActivityCallback.setNavigationButtonHidden(false); + setupActivityCallback.setCancelButtonHidden(true); + } + +}
\ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java new file mode 100644 index 00000000..e8f37e43 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java @@ -0,0 +1,153 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import static se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModel.ADD_PROVIDER; + +import android.content.Context; +import android.graphics.Typeface; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.RadioButton; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.ViewModelProvider; + +import java.util.ArrayList; + +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.base.models.Provider; +import se.leap.bitmaskclient.base.models.ProviderObservable; +import se.leap.bitmaskclient.base.utils.ViewHelper; +import se.leap.bitmaskclient.databinding.FProviderSelectionBinding; +import se.leap.bitmaskclient.providersetup.activities.CancelCallback; +import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModel; +import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModelFactory; + +public class ProviderSelectionFragment extends BaseSetupFragment implements CancelCallback { + + private ProviderSelectionViewModel viewModel; + private ArrayList<RadioButton> radioButtons; + + private FProviderSelectionBinding binding; + + public static ProviderSelectionFragment newInstance(int position) { + ProviderSelectionFragment fragment = new ProviderSelectionFragment(); + fragment.setArguments(initBundle(position)); + return fragment; + } + + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + viewModel = new ViewModelProvider(this, + new ProviderSelectionViewModelFactory( + getContext().getApplicationContext().getAssets(), + getContext().getExternalFilesDir(null))). + get(ProviderSelectionViewModel.class); + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + binding = FProviderSelectionBinding.inflate(inflater, container, false); + + radioButtons = new ArrayList<>(); + for (int i = 0; i < viewModel.size(); i++) { + RadioButton radioButton = new RadioButton(binding.getRoot().getContext()); + radioButton.setText(viewModel.getProviderName(i)); + radioButton.setId(i); + binding.providerRadioGroup.addView(radioButton); + radioButtons.add(radioButton); + } + RadioButton radioButton = new RadioButton(binding.getRoot().getContext()); + radioButton.setText(getText(R.string.add_provider)); + radioButton.setId(ADD_PROVIDER); + binding.providerRadioGroup.addView(radioButton); + radioButtons.add(radioButton); + + binding.editCustomProvider.setVisibility(viewModel.getEditProviderVisibility()); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + setupActivityCallback.registerCancelCallback(this); + } + + @Override + public void onFragmentSelected() { + super.onFragmentSelected(); + setupActivityCallback.setCancelButtonHidden(true); + setupActivityCallback.setNavigationButtonHidden(false); + binding.providerRadioGroup.setOnCheckedChangeListener((group, checkedId) -> { + viewModel.setSelected(checkedId); + for (RadioButton rb : radioButtons) { + rb.setTypeface(Typeface.DEFAULT, rb.getId() == checkedId ? Typeface.BOLD : Typeface.NORMAL); + } + binding.providerDescription.setText(viewModel.getProviderDescription(getContext())); + binding.editCustomProvider.setVisibility(viewModel.getEditProviderVisibility()); + setupActivityCallback.onSetupStepValidationChanged(viewModel.isValidConfig()); + if (checkedId != ADD_PROVIDER) { + setupActivityCallback.onProviderSelected(viewModel.getProvider(checkedId)); + } else if (viewModel.isValidConfig()) { + setupActivityCallback.onProviderSelected(new Provider(binding.editCustomProvider.getText().toString())); + } + }); + + binding.editCustomProvider.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + viewModel.setCustomUrl(s.toString()); + if (viewModel.isCustomProviderSelected()) { + setupActivityCallback.onSetupStepValidationChanged(viewModel.isValidConfig()); + if (viewModel.isValidConfig()) { + setupActivityCallback.onProviderSelected(new Provider(s.toString())); + } + } + } + + @Override + public void afterTextChanged(Editable s) {} + }); + + binding.editCustomProvider.setOnFocusChangeListener((v, hasFocus) -> { + if (!hasFocus) { + ViewHelper.hideKeyboardFrom(getContext(), v); + } + }); + binding.providerRadioGroup.check(viewModel.getSelected()); + } + + @Override + public void onDestroyView() { + setupActivityCallback.removeCancelCallback(this); + binding = null; + radioButtons = null; + super.onDestroyView(); + } + + @Override + public void onPause() { + super.onPause(); + } + + @Override + public void onResume() { + super.onResume(); + setupActivityCallback.onSetupStepValidationChanged(viewModel.isValidConfig()); + } + + @Override + public void onCanceled() { + binding.providerRadioGroup.check(0); + } +}
\ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupFragmentFactory.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupFragmentFactory.java new file mode 100644 index 00000000..eaf3fbfa --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupFragmentFactory.java @@ -0,0 +1,65 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import android.Manifest; +import android.content.Intent; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + +import java.util.ArrayList; + +public class SetupFragmentFactory { + public static final int PROVIDER_SELECTION_FRAGMENT = 0; + public static final int CIRCUMVENTION_SETUP_FRAGMENT = 1; + public static final int CONFIGURE_PROVIDER_FRAGMENT = 2; + public static final int VPN_PERMISSON_EDUCATIONAL_FRAGMENT = 3; + public static final int VPN_PERMISSON_FRAGMENT = 4; + public static final int NOTIFICATION_PERMISSON_EDUCATIONAL_FRAGMENT = 5; + public static final int NOTIFICATION_PERMISSON_FRAGMENT = 6; + + public static final int SUCCESS_FRAGMENT = 7; + + private final Intent vpnPermissionRequest; + + private final ArrayList<Integer> fragmentTypes; + + public SetupFragmentFactory(@NonNull ArrayList<Integer> fragmentTypes, Intent vpnPermissionRequest) { + this.fragmentTypes = fragmentTypes; + this.vpnPermissionRequest = vpnPermissionRequest; + } + + public Fragment createFragment(int position) { + if (position < 0 || position >= fragmentTypes.size()) { + throw new IllegalStateException("Illegal fragment position"); + } + int type = fragmentTypes.get(position); + switch (type) { + case PROVIDER_SELECTION_FRAGMENT: + return ProviderSelectionFragment.newInstance(position); + case CIRCUMVENTION_SETUP_FRAGMENT: + return CircumventionSetupFragment.newInstance(position); + case CONFIGURE_PROVIDER_FRAGMENT: + return ConfigureProviderFragment.newInstance(position); + case NOTIFICATION_PERMISSON_EDUCATIONAL_FRAGMENT: + return NotificationSetupFragment.newInstance(position); + case NOTIFICATION_PERMISSON_FRAGMENT: + return EmptyPermissionSetupFragment.newInstance(position, Manifest.permission.POST_NOTIFICATIONS); + case VPN_PERMISSON_EDUCATIONAL_FRAGMENT: + return VpnPermissionSetupFragment.newInstance(position); + case VPN_PERMISSON_FRAGMENT: + return EmptyPermissionSetupFragment.newInstance(position, vpnPermissionRequest); + case SUCCESS_FRAGMENT: + return SetupSuccessFragment.newInstance(position); + default: + throw new IllegalArgumentException("Unexpected fragment type: " + type); + } + } + + public int getItemCount() { + return fragmentTypes.size(); + } + + public int getPos(int fragmentType) { + return fragmentTypes.indexOf(fragmentType); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java new file mode 100644 index 00000000..daf4ed6c --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java @@ -0,0 +1,44 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.databinding.FSetupSuccessBinding; + +public class SetupSuccessFragment extends BaseSetupFragment { + + public static SetupSuccessFragment newInstance(int position) { + SetupSuccessFragment fragment = new SetupSuccessFragment(); + fragment.setArguments(initBundle(position)); + return fragment; + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + FSetupSuccessBinding binding = FSetupSuccessBinding.inflate(inflater, container, false); + + binding.mainButton.setOnClickListener(v -> { + setupActivityCallback.onSetupFinished(); + binding.mainButton.setEnabled(false); + binding.mainButton.setCustomDrawable(R.drawable.button_setup_circle_progress); + }); + binding.mainButton.setCustomDrawable(R.drawable.button_setup_circle_start); + + return binding.getRoot(); + } + + @Override + public void onFragmentSelected() { + super.onFragmentSelected(); + setupActivityCallback.setNavigationButtonHidden(true); + setupActivityCallback.setCancelButtonHidden(true); + } + +}
\ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/VpnPermissionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/VpnPermissionSetupFragment.java new file mode 100644 index 00000000..188ba9ac --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/VpnPermissionSetupFragment.java @@ -0,0 +1,36 @@ +package se.leap.bitmaskclient.providersetup.fragments; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import se.leap.bitmaskclient.databinding.FVpnPermissionSetupBinding; + +public class VpnPermissionSetupFragment extends BaseSetupFragment { + + public static VpnPermissionSetupFragment newInstance(int position) { + VpnPermissionSetupFragment fragment = new VpnPermissionSetupFragment(); + fragment.setArguments(initBundle(position)); + return fragment; + } + + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + FVpnPermissionSetupBinding binding = FVpnPermissionSetupBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onFragmentSelected() { + super.onFragmentSelected(); + setupActivityCallback.setNavigationButtonHidden(false); + setupActivityCallback.setCancelButtonHidden(true); + } + +}
\ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java new file mode 100644 index 00000000..aa2fe7cb --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java @@ -0,0 +1,97 @@ +package se.leap.bitmaskclient.providersetup.fragments.viewmodel; + +import android.content.Context; +import android.content.res.AssetManager; +import android.util.Patterns; +import android.view.View; +import android.webkit.URLUtil; + +import androidx.lifecycle.ViewModel; + +import java.io.File; +import java.util.List; + +import se.leap.bitmaskclient.R; +import se.leap.bitmaskclient.base.models.Provider; +import se.leap.bitmaskclient.providersetup.ProviderManager; + +public class ProviderSelectionViewModel extends ViewModel { + private final ProviderManager providerManager; + public static int ADD_PROVIDER = 100100100; + + private int selected = 0; + private String customUrl; + + public ProviderSelectionViewModel(AssetManager assetManager, File externalFilesDir) { + providerManager = ProviderManager.getInstance(assetManager, externalFilesDir); + providerManager.setAddDummyEntry(false); + } + + public int size() { + return providerManager.size(); + } + + public List<Provider> providers() { + return providerManager.providers(); + } + + public Provider getProvider(int pos) { + return providerManager.get(pos); + } + + public void setSelected(int checkedId) { + selected = checkedId; + } + + public int getSelected() { + return selected; + } + + public boolean isValidConfig() { + if (selected == ADD_PROVIDER) { + return URLUtil.isValidUrl(customUrl) && Patterns.WEB_URL.matcher(customUrl).matches(); + } + return true; + } + + public boolean isCustomProviderSelected() { + return selected == ADD_PROVIDER; + } + + public CharSequence getProviderDescription(Context context) { + if (selected == ADD_PROVIDER) { + return context.getText(R.string.add_provider_description); + } + Provider provider = getProvider(selected); + if ("riseup.net".equals(provider.getDomain())) { + return context.getText(R.string.provider_description_riseup); + } + if ("calyx.net".equals(provider.getDomain())) { + return context.getText(R.string.provider_description_calyx); + } + return provider.getDescription(); + } + + public int getEditProviderVisibility() { + if (selected == ADD_PROVIDER) { + return View.VISIBLE; + } + return View.GONE; + } + + public void setCustomUrl(String url) { + customUrl = url; + } + + + public String getProviderName(int pos) { + String domain = getProvider(pos).getDomain(); + if ("riseup.net".equals(domain)) { + return "Riseup"; + } + if ("calyx.net".equals(domain)) { + return "The Calyx Institute"; + } + return domain; + } +}
\ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModelFactory.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModelFactory.java new file mode 100644 index 00000000..a21e4924 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModelFactory.java @@ -0,0 +1,28 @@ +package se.leap.bitmaskclient.providersetup.fragments.viewmodel; + +import android.content.res.AssetManager; + +import androidx.annotation.NonNull; +import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; + +import java.io.File; + +public class ProviderSelectionViewModelFactory implements ViewModelProvider.Factory { + private final AssetManager assetManager; + private final File externalFilesDir; + + public ProviderSelectionViewModelFactory(AssetManager assetManager, File externalFilesDir) { + this.assetManager = assetManager; + this.externalFilesDir = externalFilesDir; + } + + @NonNull + @Override + public <T extends ViewModel> T create(@NonNull Class<T> modelClass) { + if (modelClass.isAssignableFrom(ProviderSelectionViewModel.class)) { + return (T) new ProviderSelectionViewModel(assetManager, externalFilesDir); + } + throw new IllegalArgumentException("Unknown ViewModel class"); + } +}
\ No newline at end of file diff --git a/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringStateManager.java b/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringStateManager.java index d74175f5..926affe0 100644 --- a/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringStateManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringStateManager.java @@ -62,9 +62,9 @@ public class TetheringStateManager { intentFilter.addAction("android.net.wifi.WIFI_AP_STATE_CHANGED"); context.getApplicationContext().registerReceiver(broadcastReceiver, intentFilter); instance.wifiManager = new WifiManagerWrapper(context); - TetheringObservable.allowVpnWifiTethering(isWifiTetheringAllowed(context)); - TetheringObservable.allowVpnUsbTethering(isUsbTetheringAllowed(context)); - TetheringObservable.allowVpnBluetoothTethering(isBluetoothTetheringAllowed(context)); + TetheringObservable.allowVpnWifiTethering(isWifiTetheringAllowed()); + TetheringObservable.allowVpnUsbTethering(isUsbTetheringAllowed()); + TetheringObservable.allowVpnBluetoothTethering(isBluetoothTetheringAllowed()); updateWifiTetheringState(); updateUsbTetheringState(); updateBluetoothTetheringState(); diff --git a/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java b/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java index b99abb3d..9e95700c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java +++ b/app/src/main/java/se/leap/bitmaskclient/tor/TorServiceCommand.java @@ -15,6 +15,10 @@ package se.leap.bitmaskclient.tor; * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +import static se.leap.bitmaskclient.tor.TorNotificationManager.TOR_SERVICE_NOTIFICATION_ID; +import static se.leap.bitmaskclient.tor.TorStatusObservable.waitUntil; + import android.app.Notification; import android.content.Context; import android.content.Intent; @@ -29,9 +33,6 @@ import java.util.concurrent.TimeoutException; import se.leap.bitmaskclient.base.utils.PreferenceHelper; -import static se.leap.bitmaskclient.tor.TorNotificationManager.TOR_SERVICE_NOTIFICATION_ID; -import static se.leap.bitmaskclient.tor.TorStatusObservable.waitUntil; - public class TorServiceCommand { @@ -132,7 +133,7 @@ public class TorServiceCommand { private static TorServiceConnection initTorServiceConnection(Context context) throws InterruptedException, IllegalStateException { Log.d(TAG, "initTorServiceConnection"); - if (PreferenceHelper.getUseSnowflake(context)) { + if (PreferenceHelper.getUseSnowflake()) { Log.d(TAG, "serviceConnection is still null"); if (!TorService.hasClientTransportPlugin()) { TorService.setClientTransportPlugin(new ClientTransportPlugin(context.getApplicationContext())); diff --git a/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java b/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java index 36ab66bf..8bb41dd2 100644 --- a/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java +++ b/app/src/main/java/se/leap/bitmaskclient/tor/TorStatusObservable.java @@ -20,6 +20,7 @@ import static se.leap.bitmaskclient.tor.TorStatusObservable.SnowflakeStatus.NEGO import static se.leap.bitmaskclient.tor.TorStatusObservable.SnowflakeStatus.NEGOTIATING_RENDEZVOUS_VIA_HTTP; import static se.leap.bitmaskclient.tor.TorStatusObservable.SnowflakeStatus.RETRY_AMP_CACHE_RENDEZVOUS; import static se.leap.bitmaskclient.tor.TorStatusObservable.SnowflakeStatus.RETRY_HTTP_RENDEZVOUS; +import static se.leap.bitmaskclient.tor.TorStatusObservable.SnowflakeStatus.SENDING_DATA; import static se.leap.bitmaskclient.tor.TorStatusObservable.SnowflakeStatus.STARTED; import static se.leap.bitmaskclient.tor.TorStatusObservable.SnowflakeStatus.STOPPED; @@ -60,6 +61,7 @@ public class TorStatusObservable extends Observable { RETRY_HTTP_RENDEZVOUS, RETRY_AMP_CACHE_RENDEZVOUS, BROKER_REPLIED_SUCCESS, + SENDING_DATA, STOPPED } @@ -79,6 +81,9 @@ public class TorStatusObservable extends Observable { public static final String SNOWFLAKE_HTTP_RESPONSE_200 = "HTTP rendezvous response: 200"; public static final String SNOWFLAKE_AMP_CACHE_RESPONSE_200 = "AMP cache rendezvous response: 200"; + public static final String SNOWFLAKE_SENDING_DATA = "Traffic Bytes (in|out):"; + + private static TorStatusObservable instance; private TorStatus status = TorStatus.OFF; private SnowflakeStatus snowflakeStatus = STOPPED; @@ -142,7 +147,6 @@ public class TorStatusObservable extends Observable { public static void logSnowflakeMessage(Context context, String message) { addLog(message); - getInstance().lastSnowflakeLog = message; if (getInstance().status != TorStatus.OFF) { getInstance().torNotificationManager.buildTorNotification(context, getStringForCurrentStatus(context), getNotificationLog(), getBootstrapProgress()); } @@ -150,14 +154,18 @@ public class TorStatusObservable extends Observable { message = message.trim(); if (SNOWFLAKE_STARTED.equals(message)) { getInstance().snowflakeStatus = STARTED; + getInstance().lastSnowflakeLog = context.getString(R.string.snowflake_started); } else if (SNOWFLAKE_NEGOTIATING_HTTP.equals(message)) { getInstance().snowflakeStatus = NEGOTIATING_RENDEZVOUS_VIA_HTTP; + getInstance().lastSnowflakeLog = context.getString(R.string.snowflake_negotiating_rendezvous_http); } else if (SNOWFLAKE_NEGOTIATING_AMP_CACHE.equals(message)) { getInstance().snowflakeStatus = NEGOTIATING_RENDEZVOUS_VIA_AMP_CACHE; + getInstance().lastSnowflakeLog = context.getString(R.string.snowflake_negotiating_rendezvous_amp_cache); } else if (SNOWFLAKE_STOPPED_COLLECTING.equals(message) || SNOWFLAKE_COPY_LOOP_STOPPED.equals(message) || message.contains(SNOWFLAKE_SOCKS_ERROR)) { getInstance().snowflakeStatus = STOPPED; + getInstance().lastSnowflakeLog = context.getString(R.string.snowflake_socks_error); } else if (SNOWFLAKE_CONNECTION_CLOSING.equals(message)) { if (getInstance().snowflakeStatus == NEGOTIATING_RENDEZVOUS_VIA_HTTP) { if (getInstance().retrySnowflakeRendezVous < 3) { @@ -177,6 +185,10 @@ public class TorStatusObservable extends Observable { } else if (SNOWFLAKE_AMP_CACHE_RESPONSE_200.equals(message) || SNOWFLAKE_HTTP_RESPONSE_200.equals(message)) { getInstance().snowflakeStatus = BROKER_REPLIED_SUCCESS; getInstance().retrySnowflakeRendezVous = 0; + getInstance().lastSnowflakeLog = context.getString(R.string.snowflake_broker_success); + } else if (message.contains(SNOWFLAKE_SENDING_DATA)) { + getInstance().snowflakeStatus = SENDING_DATA; + getInstance().lastSnowflakeLog = context.getString(R.string.snowflake_sending_data); } Log.d(TAG, "snowflake status " + getInstance().snowflakeStatus); instance.setChanged(); @@ -195,7 +207,14 @@ public class TorStatusObservable extends Observable { } public static int getBootstrapProgress() { - return getInstance().status == TorStatus.STARTING ? getInstance().bootstrapPercent : -1; + switch (getInstance().status) { + case STARTING: + return getInstance().bootstrapPercent; + case ON: + return 100; + default: + return -1; + } } private static void addLog(String message) { diff --git a/app/src/main/res/drawable-anydpi-v24/ic_arrow_forward.xml b/app/src/main/res/drawable-anydpi-v24/ic_arrow_forward.xml new file mode 100644 index 00000000..fb6b1ad8 --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v24/ic_arrow_forward.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="17dp" + android:height="17dp" + android:viewportWidth="17" + android:viewportHeight="17"> + <path + android:pathData="M1.417,5.684L2.674,4.427L8.5,10.253L14.326,4.427L15.583,5.684L8.5,12.768L1.417,5.684Z" + android:fillColor="#1C1B1F"/> +</vector> diff --git a/app/src/main/res/drawable-anydpi-v24/ic_back.xml b/app/src/main/res/drawable-anydpi-v24/ic_back.xml new file mode 100644 index 00000000..404c8177 --- /dev/null +++ b/app/src/main/res/drawable-anydpi-v24/ic_back.xml @@ -0,0 +1 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#FFFFFF" android:pathData="M21,11H6.83L10.41,7.41L9,6L3,12L9,18L10.41,16.58L6.83,13H21V11Z" /></vector>
\ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/bitmask_text.png b/app/src/main/res/drawable-hdpi/bitmask_text.png Binary files differdeleted file mode 100644 index 71c0d171..00000000 --- a/app/src/main/res/drawable-hdpi/bitmask_text.png +++ /dev/null diff --git a/app/src/main/res/drawable-xhdpi/bitmask_text.png b/app/src/main/res/drawable-xhdpi/bitmask_text.png Binary files differdeleted file mode 100644 index f0f8f238..00000000 --- a/app/src/main/res/drawable-xhdpi/bitmask_text.png +++ /dev/null diff --git a/app/src/main/res/drawable-xxhdpi/bitmask_text.png b/app/src/main/res/drawable-xxhdpi/bitmask_text.png Binary files differdeleted file mode 100644 index 411c2946..00000000 --- a/app/src/main/res/drawable-xxhdpi/bitmask_text.png +++ /dev/null diff --git a/app/src/main/res/drawable-xxxhdpi/bitmask_text.png b/app/src/main/res/drawable-xxxhdpi/bitmask_text.png Binary files differdeleted file mode 100644 index a73f4526..00000000 --- a/app/src/main/res/drawable-xxxhdpi/bitmask_text.png +++ /dev/null diff --git a/app/src/main/res/drawable/bitmask_text.png b/app/src/main/res/drawable/bitmask_text.png Binary files differdeleted file mode 100644 index 9a13b150..00000000 --- a/app/src/main/res/drawable/bitmask_text.png +++ /dev/null diff --git a/app/src/main/res/drawable/button_setup_circle_progress.xml b/app/src/main/res/drawable/button_setup_circle_progress.xml new file mode 100644 index 00000000..be45d207 --- /dev/null +++ b/app/src/main/res/drawable/button_setup_circle_progress.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/button_setup_circle_progress_pressed" android:state_pressed="true"> + </item> + <item android:drawable="@drawable/button_setup_circle_progress_released"> + </item> +</selector>
\ No newline at end of file diff --git a/app/src/main/res/drawable/button_setup_circle_progress_pressed.xml b/app/src/main/res/drawable/button_setup_circle_progress_pressed.xml new file mode 100644 index 00000000..d83a43cb --- /dev/null +++ b/app/src/main/res/drawable/button_setup_circle_progress_pressed.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- <item> + <shape android:shape="rectangle"> + <solid android:color="@color/white"/> + <size android:width="250dp" android:height="250dp"/> + </shape> + </item> --> + <item + android:left="-23dp" + android:right="-23dp" + android:top="-8dp" + android:bottom="-8dp" + > + <animated-rotate + android:drawable="@drawable/rotate_progress_image" + android:pivotX="50.0%" + android:pivotY="50.0%" + android:fromDegrees="0.0" + android:toDegrees="360.0" + > + </animated-rotate> + </item> + <item + android:top="18dp" + android:bottom="12dp" + > + <shape android:shape="oval"> + <solid android:color="@color/colorPrimary"/> + <size android:width="250dp" android:height="250dp" /> + </shape> + </item> + <item + android:top="18dp" + android:bottom="12dp" + > + <shape + android:shape="ring" + android:innerRadius="125dp" + android:useLevel="false" + android:thickness="3dp"> + <gradient + android:type="radial" + android:gradientRadius="125dp" + android:centerX="0.50" + android:centerY="0.54" + android:startColor="#000000" + android:centerColor="#000000" + android:endColor="@color/transparent" /> + + </shape> + </item> + <item + android:top="78dp" + android:bottom="72dp" + android:left="60dp" + android:right="60dp" + android:drawable="@drawable/ic_btn_on" /> + +</layer-list>
\ No newline at end of file diff --git a/app/src/main/res/drawable/button_setup_circle_progress_released.xml b/app/src/main/res/drawable/button_setup_circle_progress_released.xml new file mode 100644 index 00000000..9c4ac6d6 --- /dev/null +++ b/app/src/main/res/drawable/button_setup_circle_progress_released.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- <item> + <shape android:shape="rectangle"> + <solid android:color="@color/white"/> + <size android:width="250dp" android:height="250dp"/> + </shape> + </item>--> + + <item + android:left="-25dp" + android:right="-25dp" + android:top="-10dp" + android:bottom="-10dp" + > + <animated-rotate + android:drawable="@drawable/rotate_progress_image" + android:pivotX="50.0%" + android:pivotY="50.0%" + android:fromDegrees="0.0" + android:toDegrees="360.0" + > + </animated-rotate> + </item> + <item + android:bottom="15dp" + android:top="15dp" + > + <shape android:shape="oval"> + <solid android:color="@color/colorPrimaryLight"/> + <size android:width="250dp" android:height="250dp" /> + </shape> + </item> + <item + android:bottom="15dp" + android:top="15dp" + > + <shape + android:shape="ring" + android:innerRadius="123dp" + android:useLevel="false" + android:thickness="15dp"> + <gradient + android:type="radial" + android:gradientRadius="125dp" + android:centerX="0.51" + android:centerY="0.55" + android:startColor="#000000" + android:centerColor="#000000" + android:endColor="@color/transparent" /> + + </shape> + </item> + <item + android:top="75dp" + android:bottom="75dp" + android:left="60dp" + android:right="60dp" + android:drawable="@drawable/ic_btn_on" + /> +</layer-list>
\ No newline at end of file diff --git a/app/src/main/res/drawable/button_setup_circle_start.xml b/app/src/main/res/drawable/button_setup_circle_start.xml new file mode 100644 index 00000000..93665361 --- /dev/null +++ b/app/src/main/res/drawable/button_setup_circle_start.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:drawable="@drawable/button_setup_circle_start_pressed" android:state_pressed="true"> + </item> + <item android:drawable="@drawable/button_setup_circle_start_released"> + </item> +</selector>
\ No newline at end of file diff --git a/app/src/main/res/drawable/button_setup_circle_start_pressed.xml b/app/src/main/res/drawable/button_setup_circle_start_pressed.xml new file mode 100644 index 00000000..dd0caf4c --- /dev/null +++ b/app/src/main/res/drawable/button_setup_circle_start_pressed.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + > + <!--<item> + <shape android:shape="rectangle"> + <solid android:color="@color/white"/> + <size android:width="250dp" android:height="250dp"/> + </shape> + </item>--> + <item + android:top="18dp" + android:bottom="12dp" + > + <shape android:shape="oval"> + <solid android:color="@color/colorPrimary"/> + <size android:width="250dp" android:height="250dp" /> + </shape> + </item> + <item + android:top="18dp" + android:bottom="12dp" + > + <shape + android:shape="ring" + android:innerRadius="125dp" + android:useLevel="false" + android:thickness="3dp"> + <gradient + android:type="radial" + android:gradientRadius="125dp" + android:centerX="0.50" + android:centerY="0.54" + android:startColor="#000000" + android:centerColor="#000000" + android:endColor="@color/transparent" /> + + </shape> + </item> + <item + android:top="78dp" + android:bottom="72dp" + android:left="60dp" + android:right="60dp" + android:drawable="@drawable/ic_btn_on" + /> +</layer-list>
\ No newline at end of file diff --git a/app/src/main/res/drawable/button_setup_circle_start_released.xml b/app/src/main/res/drawable/button_setup_circle_start_released.xml new file mode 100644 index 00000000..da1cb120 --- /dev/null +++ b/app/src/main/res/drawable/button_setup_circle_start_released.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <!--<item> + <shape android:shape="rectangle"> + <solid android:color="@color/white"/> + <size android:width="250dp" android:height="250dp"/> + </shape> + </item>--> + <item + android:bottom="15dp" + android:top="15dp" + > + <shape android:shape="oval"> + <solid android:color="@color/colorPrimaryLight"/> + <size android:width="250dp" android:height="250dp" /> + </shape> + </item> + <item + android:bottom="15dp" + android:top="15dp" + > + <shape + android:shape="ring" + android:innerRadius="122dp" + android:useLevel="false" + android:thickness="15dp"> + <gradient + android:type="radial" + android:gradientRadius="125dp" + android:centerX="0.51" + android:centerY="0.55" + android:startColor="#000000" + android:centerColor="#000000" + android:endColor="@color/transparent" /> + + </shape> + </item> + <item + android:top="75dp" + android:bottom="75dp" + android:left="60dp" + android:right="60dp" + android:drawable="@drawable/ic_btn_on" /> +</layer-list>
\ No newline at end of file diff --git a/app/src/main/res/drawable/cust_setup_button_primary.xml b/app/src/main/res/drawable/cust_setup_button_primary.xml new file mode 100644 index 00000000..245544c3 --- /dev/null +++ b/app/src/main/res/drawable/cust_setup_button_primary.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android" > + + <item android:state_enabled="false" android:drawable="@drawable/cust_setup_button_primary_disabled"/> + <item android:state_pressed="true" android:drawable="@drawable/cust_setup_button_primary_pressed"/> + <item android:state_focused="true" android:drawable="@drawable/cust_setup_button_primary_pressed"/> + <item android:drawable="@drawable/cust_setup_button_primary_released"/> +</selector>
\ No newline at end of file diff --git a/app/src/main/res/drawable/cust_setup_button_primary_disabled.xml b/app/src/main/res/drawable/cust_setup_button_primary_disabled.xml new file mode 100644 index 00000000..a8ccfbe1 --- /dev/null +++ b/app/src/main/res/drawable/cust_setup_button_primary_disabled.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + <item> + <shape android:shape="rectangle" > + <corners android:radius="8dp" /> + <solid android:color="@color/colorPrimary_transparent"/> + </shape> + </item> + <item + android:bottom="@dimen/button_bevel" + > + <shape android:shape="rectangle"> + <corners android:radius="8dp" /> + <padding android:right="2dp"/> + <padding android:left="8dp" android:right="8dp"/> + <solid android:color="@color/colorDisabled"/> + </shape> + </item> +</layer-list>
\ No newline at end of file diff --git a/app/src/main/res/drawable/cust_setup_button_primary_pressed.xml b/app/src/main/res/drawable/cust_setup_button_primary_pressed.xml new file mode 100644 index 00000000..6fb664d9 --- /dev/null +++ b/app/src/main/res/drawable/cust_setup_button_primary_pressed.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + <item> + <shape android:shape="rectangle" > + <corners android:radius="8dp" /> + <padding android:left="8dp" android:right="8dp"/> + <solid android:color="@color/colorPrimaryDark"/> + </shape> + </item> +</layer-list>
\ No newline at end of file diff --git a/app/src/main/res/drawable/cust_setup_button_primary_released.xml b/app/src/main/res/drawable/cust_setup_button_primary_released.xml new file mode 100644 index 00000000..80fc091b --- /dev/null +++ b/app/src/main/res/drawable/cust_setup_button_primary_released.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + <item> + <shape android:shape="rectangle" > + <corners android:radius="8dp" /> + <solid android:color="@color/colorPrimary_transparent"/> + </shape> + </item> + <item + android:bottom="@dimen/button_bevel" + > + <shape android:shape="rectangle" > + <corners android:radius="8dp" /> + <padding android:right="2dp"/> + <padding android:left="8dp" android:right="8dp"/> + <solid android:color="@color/colorPrimary"/> + </shape> + </item> +</layer-list>
\ No newline at end of file diff --git a/app/src/main/res/drawable/footer_text_drawable.xml b/app/src/main/res/drawable/footer_text_drawable.xml deleted file mode 100644 index e3ec5005..00000000 --- a/app/src/main/res/drawable/footer_text_drawable.xml +++ /dev/null @@ -1,21 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> - -<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item - android:gravity="fill_horizontal"> - - <shape android:shape="rectangle" - xmlns:android="http://schemas.android.com/apk/res/android"> - <solid android:color="@android:color/white"/> - </shape> - </item> - <item - android:gravity="center" - android:top="@dimen/footer_text_padding" - android:bottom="@dimen/footer_text_padding" - android:left="20dp" - android:right="20dp" - > - <bitmap android:src="@drawable/leap_footer_en"/> - </item> -</layer-list> diff --git a/app/src/main/res/drawable/ic_arrow_forward.png b/app/src/main/res/drawable/ic_arrow_forward.png Binary files differnew file mode 100644 index 00000000..2b45341c --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_forward.png diff --git a/app/src/main/res/drawable/ic_back.png b/app/src/main/res/drawable/ic_back.png Binary files differnew file mode 100644 index 00000000..dcc52bfa --- /dev/null +++ b/app/src/main/res/drawable/ic_back.png diff --git a/app/src/main/res/drawable/leap_footer_en.png b/app/src/main/res/drawable/leap_footer_en.png Binary files differdeleted file mode 100644 index 4881eb29..00000000 --- a/app/src/main/res/drawable/leap_footer_en.png +++ /dev/null diff --git a/app/src/main/res/drawable/progress_spinner.xml b/app/src/main/res/drawable/progress_spinner.xml new file mode 100644 index 00000000..ae2c4f69 --- /dev/null +++ b/app/src/main/res/drawable/progress_spinner.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:left="-25dp" + android:right="-25dp" + android:top="-10dp" + android:bottom="-10dp" + > + <animated-rotate + android:drawable="@drawable/rotate_progress_image" + android:pivotX="50.0%" + android:pivotY="50.0%" + android:fromDegrees="0.0" + android:toDegrees="360.0" + > + </animated-rotate> + </item> + <item + android:bottom="15dp" + android:top="15dp" + > + <shape android:shape="oval"> + <solid android:color="@color/white"/> + <size android:width="250dp" android:height="250dp" /> + </shape> + </item> + +</layer-list>
\ No newline at end of file diff --git a/app/src/main/res/drawable/splash_page.xml b/app/src/main/res/drawable/splash_page.xml index 92ae59ef..aafac0db 100644 --- a/app/src/main/res/drawable/splash_page.xml +++ b/app/src/main/res/drawable/splash_page.xml @@ -4,15 +4,9 @@ <item android:drawable="@drawable/ic_splash_background" android:gravity="fill_horizontal|fill_vertical"/> + <item - android:top="@dimen/splash_text_top_padding" - > - <bitmap - android:src="@drawable/bitmask_text" - android:gravity="center_horizontal|top" /> - </item> - <item - android:drawable="@drawable/footer_text_drawable" - android:gravity="bottom|fill_horizontal" /> + android:drawable="@drawable/splash_icon" + android:gravity="center" /> </layer-list>
\ No newline at end of file diff --git a/app/src/main/res/layout-port/f_setup_success.xml b/app/src/main/res/layout-port/f_setup_success.xml new file mode 100644 index 00000000..7b13261f --- /dev/null +++ b/app/src/main/res/layout-port/f_setup_success.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> + + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".providersetup.fragments.ProviderSelectionFragment"> + + <androidx.constraintlayout.widget.Guideline + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:id="@+id/top_button_guideline" + app:layout_constraintGuide_percent="0.6" + /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_vertical_left" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.3" + /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_vertical_right" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.7" + /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/bottom_button_guideline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_end="72dp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="@dimen/stdpadding" + android:layout_margin="@dimen/activity_margin" + app:layout_constraintTop_toTopOf="parent" + > + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.AppCompat.Title" + android:text="@string/title_setup_success" + android:paddingBottom="@dimen/stdpadding" + /> + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_setup_success_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintTop_toBottomOf="@id/tv_title" + android:textAppearance="@style/TextAppearance.AppCompat.Body1" + android:text="@string/setup_success_description"/> + </LinearLayout> + + <se.leap.bitmaskclient.base.views.MainButton + android:id="@+id/main_button" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="@dimen/mainbutton_padding" + app:layout_constraintTop_toBottomOf="@id/top_button_guideline" + app:layout_constraintBottom_toTopOf="@id/bottom_button_guideline" + app:layout_constraintDimensionRatio="1:1" + app:layout_constraintEnd_toStartOf="@+id/guideline_vertical_right" + app:layout_constraintStart_toStartOf="@+id/guideline_vertical_left" + /> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp-port/f_setup_success.xml b/app/src/main/res/layout-sw600dp-port/f_setup_success.xml new file mode 100644 index 00000000..52fca8dd --- /dev/null +++ b/app/src/main/res/layout-sw600dp-port/f_setup_success.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> + + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".providersetup.fragments.ProviderSelectionFragment"> + + <androidx.constraintlayout.widget.Guideline + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:id="@+id/top_button_guideline" + app:layout_constraintGuide_percent="0.6" + /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_vertical_left" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.3" + /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_vertical_right" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.7" + /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/bottom_button_guideline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_end="82dp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="@dimen/stdpadding" + android:layout_margin="@dimen/activity_margin" + app:layout_constraintTop_toTopOf="parent" + > + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.AppCompat.Title" + android:text="@string/title_setup_success" + android:paddingBottom="@dimen/stdpadding" + /> + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_setup_success_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintTop_toBottomOf="@id/tv_title" + android:textAppearance="@style/TextAppearance.AppCompat.Body1" + android:text="@string/setup_success_description"/> + </LinearLayout> + + <se.leap.bitmaskclient.base.views.MainButton + android:id="@+id/main_button" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="@dimen/mainbutton_padding" + app:layout_constraintTop_toBottomOf="@id/top_button_guideline" + app:layout_constraintBottom_toTopOf="@id/bottom_button_guideline" + app:layout_constraintDimensionRatio="1:1" + app:layout_constraintEnd_toStartOf="@+id/guideline_vertical_right" + app:layout_constraintStart_toStartOf="@+id/guideline_vertical_left" + /> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout-xlarge-port/f_setup_success.xml b/app/src/main/res/layout-xlarge-port/f_setup_success.xml new file mode 100644 index 00000000..f92ef4db --- /dev/null +++ b/app/src/main/res/layout-xlarge-port/f_setup_success.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> + + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".providersetup.fragments.ProviderSelectionFragment"> + + <androidx.constraintlayout.widget.Guideline + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:id="@+id/top_button_guideline" + app:layout_constraintGuide_percent="0.65" + /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_vertical_left" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.3" + /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_vertical_right" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.7" + /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/bottom_button_guideline" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.875" + /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="@dimen/stdpadding" + android:layout_margin="@dimen/activity_margin" + app:layout_constraintTop_toTopOf="parent" + > + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.AppCompat.Title" + android:text="@string/title_setup_success" + android:paddingBottom="@dimen/stdpadding" + /> + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_setup_success_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintTop_toBottomOf="@id/tv_title" + android:textAppearance="@style/TextAppearance.AppCompat.Body1" + android:text="@string/setup_success_description"/> + </LinearLayout> + + <se.leap.bitmaskclient.base.views.MainButton + android:id="@+id/main_button" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_margin="@dimen/mainbutton_padding" + app:layout_constraintTop_toBottomOf="@id/top_button_guideline" + app:layout_constraintBottom_toTopOf="@id/bottom_button_guideline" + app:layout_constraintDimensionRatio="1:1" + app:layout_constraintEnd_toStartOf="@+id/guideline_vertical_right" + app:layout_constraintStart_toStartOf="@+id/guideline_vertical_left" + /> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout-xlarge/f_setup_success.xml b/app/src/main/res/layout-xlarge/f_setup_success.xml new file mode 100644 index 00000000..9a336a92 --- /dev/null +++ b/app/src/main/res/layout-xlarge/f_setup_success.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> + + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".providersetup.fragments.ProviderSelectionFragment"> + + <androidx.constraintlayout.widget.Guideline + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:id="@+id/top_button_guideline" + app:layout_constraintGuide_percent="0.6" + /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_vertical_left" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.3" + /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_vertical_right" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.7" + /> + + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/bottom_button_guideline" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.875" + /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="@dimen/stdpadding" + android:layout_margin="@dimen/activity_margin" + app:layout_constraintTop_toTopOf="parent" + > + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.AppCompat.Title" + android:text="@string/title_setup_success" + android:paddingBottom="@dimen/stdpadding" + /> + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_setup_success_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintTop_toBottomOf="@id/tv_title" + android:textAppearance="@style/TextAppearance.AppCompat.Body1" + android:text="@string/setup_success_description"/> + </LinearLayout> + + <se.leap.bitmaskclient.base.views.MainButton + android:id="@+id/main_button" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintTop_toBottomOf="@id/top_button_guideline" + app:layout_constraintBottom_toTopOf="@id/bottom_button_guideline" + app:layout_constraintDimensionRatio="1:1" + app:layout_constraintEnd_toStartOf="@+id/guideline_vertical_right" + app:layout_constraintStart_toStartOf="@+id/guideline_vertical_left" + /> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/activity_setup.xml b/app/src/main/res/layout/activity_setup.xml new file mode 100644 index 00000000..5fff1154 --- /dev/null +++ b/app/src/main/res/layout/activity_setup.xml @@ -0,0 +1,158 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:weightSum="1" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <androidx.appcompat.widget.Toolbar + android:id="@+id/toolbar" + android:minHeight="?attr/actionBarSize" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/colorPrimary"> + </androidx.appcompat.widget.Toolbar> + + <androidx.viewpager2.widget.ViewPager2 + android:id="@+id/viewPager" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_alignParentTop="true" + android:layout_below="@id/toolbar" + android:layout_alignParentBottom="true" + android:layout_weight="1" + /> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="@dimen/setup_bottom_bar" + android:layout_alignParentBottom="true" + android:padding="@dimen/stdpadding" + android:background="@color/colorPrimary_transparent" + > + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/indicatorContainer" + android:orientation="horizontal" + android:gravity="center" + android:layout_centerInParent="true" + > + <androidx.cardview.widget.CardView + android:layout_width="12dp" + android:layout_height="12dp" + app:cardCornerRadius="6dp" + app:cardElevation="0dp" + android:layout_margin="4dp" + > + <View + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/colorPrimary"/> + </androidx.cardview.widget.CardView> + <androidx.cardview.widget.CardView + android:layout_width="12dp" + android:layout_height="12dp" + app:cardCornerRadius="6dp" + app:cardElevation="0dp" + android:layout_margin="4dp" + > + <View + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/colorPrimary"/> + </androidx.cardview.widget.CardView> + <androidx.cardview.widget.CardView + android:layout_width="12dp" + android:layout_height="12dp" + app:cardCornerRadius="6dp" + app:cardElevation="0dp" + android:layout_margin="4dp" + > + <View + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/colorPrimary"/> + </androidx.cardview.widget.CardView> + <androidx.cardview.widget.CardView + android:layout_width="12dp" + android:layout_height="12dp" + app:cardCornerRadius="6dp" + app:cardElevation="0dp" + android:layout_margin="4dp" + android:visibility="gone" + > + <View + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/colorPrimary"/> + </androidx.cardview.widget.CardView> + <androidx.cardview.widget.CardView + android:layout_width="12dp" + android:layout_height="12dp" + app:cardCornerRadius="6dp" + app:cardElevation="0dp" + android:layout_margin="4dp" + android:visibility="gone" + > + <View + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/colorPrimary"/> + </androidx.cardview.widget.CardView> + <androidx.cardview.widget.CardView + android:layout_width="12dp" + android:layout_height="12dp" + app:cardCornerRadius="6dp" + app:cardElevation="0dp" + android:layout_margin="4dp" + android:visibility="gone" + > + <View + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/colorPrimary"/> + </androidx.cardview.widget.CardView> + <androidx.cardview.widget.CardView + android:layout_width="12dp" + android:layout_height="12dp" + app:cardCornerRadius="6dp" + app:cardElevation="0dp" + android:layout_margin="4dp" + android:visibility="gone" + > + <View + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/colorPrimary"/> + </androidx.cardview.widget.CardView> + + </LinearLayout> + <androidx.appcompat.widget.AppCompatButton + style="@style/BitmaskSetupButton" + android:id="@+id/setup_next_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_alignParentBottom="true" + android:layout_centerVertical="true" + android:text="@string/next" + tools:visibility="visible" + + /> + + <androidx.appcompat.widget.AppCompatButton + style="@style/BitmaskSetupButton" + android:id="@+id/setup_cancel_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:text="@string/cancel" + android:visibility="gone" + tools:visibility="visible" + /> + </RelativeLayout> +</androidx.appcompat.widget.LinearLayoutCompat> diff --git a/app/src/main/res/layout/f_circumvention_setup.xml b/app/src/main/res/layout/f_circumvention_setup.xml new file mode 100644 index 00000000..9f303780 --- /dev/null +++ b/app/src/main/res/layout/f_circumvention_setup.xml @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="utf-8"?> + + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="@dimen/stdpadding" + android:layout_margin="@dimen/activity_margin" + tools:context=".providersetup.fragments.ProviderSelectionFragment"> + + <androidx.appcompat.widget.LinearLayoutCompat + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_margin="@dimen/activity_margin" + > + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.AppCompat.Title" + android:text="@string/title_circumvention_setup" + android:paddingBottom="@dimen/stdpadding" + /> + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_circumvention_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.AppCompat.Body1" + android:text="@string/circumvention_setup_description"/> + + <androidx.cardview.widget.CardView + android:id="@+id/cv_provider_selection_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:cardElevation="2dp" + app:cardUseCompatPadding="true" + app:cardCornerRadius="12dp" + android:layout_marginTop="40dp" + > + <androidx.appcompat.widget.LinearLayoutCompat + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:background="@color/colorPrimary_transparent" + android:padding="@dimen/activity_horizontal_margin" + > + <RadioGroup + android:id="@+id/circumvention_radioGroup" + android:layout_width="match_parent" + android:layout_height="wrap_content" + > + <com.google.android.material.radiobutton.MaterialRadioButton + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/rb_plain_vpn" + android:text="@string/use_standard_vpn" + /> + <com.google.android.material.radiobutton.MaterialRadioButton + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/rb_circumvention" + android:text="@string/use_circumvention_tech" + /> + </RadioGroup> + <androidx.appcompat.widget.AppCompatTextView + android:paddingTop="@dimen/stdpadding" + android:id="@+id/tv_circumvention_detail_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/circumvention_setup_hint"/> + </androidx.appcompat.widget.LinearLayoutCompat> + </androidx.cardview.widget.CardView> + </androidx.appcompat.widget.LinearLayoutCompat> +</ScrollView>
\ No newline at end of file diff --git a/app/src/main/res/layout/f_configure_provider.xml b/app/src/main/res/layout/f_configure_provider.xml new file mode 100644 index 00000000..5841ee13 --- /dev/null +++ b/app/src/main/res/layout/f_configure_provider.xml @@ -0,0 +1,236 @@ +<?xml version="1.0" encoding="utf-8"?> + + +<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="@dimen/stdpadding" + android:layout_marginTop="@dimen/activity_margin" + android:layout_marginStart="@dimen/activity_margin" + android:layout_marginEnd="@dimen/activity_margin" + tools:context=".providersetup.fragments.ProviderSelectionFragment"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_marginTop="@dimen/activity_margin" + android:layout_marginStart="@dimen/activity_margin" + android:layout_marginEnd="@dimen/activity_margin" + > + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.AppCompat.Title" + android:text="@string/configuring_provider" + android:paddingBottom="@dimen/stdpadding" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + /> + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_circumvention_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.AppCompat.Body1" + android:text="@string/description_configure_provider" + app:layout_constraintTop_toBottomOf="@id/tv_title" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + /> + + <se.leap.bitmaskclient.base.views.ProgressSpinner + android:id="@+id/progress_spinner" + android:layout_width="100dp" + android:layout_height="100dp" + app:layout_constraintTop_toBottomOf="@id/tv_circumvention_description" + app:layout_constraintStart_toStartOf="parent" + /> + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_progress_status" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:maxLines="2" + android:ellipsize="end" + app:layout_constraintBottom_toBottomOf="@id/progress_spinner" + app:layout_constraintTop_toTopOf="@id/progress_spinner" + app:layout_constraintStart_toEndOf="@id/progress_spinner" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + tools:text="Starting bridges this is a very long text 123" + /> + + <androidx.cardview.widget.CardView + android:id="@+id/detail_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:cardElevation="2dp" + app:cardUseCompatPadding="true" + app:cardCornerRadius="12dp" + app:layout_constraintTop_toBottomOf="@id/progress_spinner" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintVertical_bias="0" + > + <androidx.appcompat.widget.LinearLayoutCompat + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:background="@color/colorPrimary_transparent" + > + <androidx.appcompat.widget.LinearLayoutCompat + android:id="@+id/detail_header_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:gravity="center" + android:orientation="horizontal" + android:padding="@dimen/stdpadding" + android:clickable="true" + android:focusable="true"> + <androidx.appcompat.widget.AppCompatImageView + android:id="@+id/iv_expand" + android:layout_width="18dp" + android:layout_height="18dp" + android:src="@drawable/ic_arrow_forward"/> + <androidx.appcompat.widget.AppCompatTextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/details" + android:textStyle="bold" + android:paddingHorizontal="@dimen/stdpadding"/> + </androidx.appcompat.widget.LinearLayoutCompat> + + <androidx.appcompat.widget.LinearLayoutCompat + android:id="@+id/expandable_detail_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:visibility="gone" + tools:visibility="visible" + > + <androidx.constraintlayout.widget.ConstraintLayout + android:id="@+id/connection_detail_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="visible" + > + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_tor_status" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintTop_toTopOf="parent" + android:text="@string/tor_status" + android:textStyle="bold" + android:paddingHorizontal="@dimen/stdpadding"/> + + <androidx.appcompat.widget.AppCompatImageView + android:id="@+id/tor_icon" + android:layout_width="35dp" + android:layout_height="35dp" + app:layout_constraintTop_toBottomOf="@id/tv_tor_status" + app:layout_constraintStart_toStartOf="parent" + android:padding="4dp" + android:src="@drawable/ic_tor" /> + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tor_state" + android:layout_width="0dp" + android:layout_height="0dp" + android:fadingEdge="horizontal" + android:maxLines="2" + android:text="@string/configuring_provider" + android:textAppearance="@style/Base.TextAppearance.AppCompat.Small" + app:layout_constraintTop_toTopOf="@id/tor_icon" + app:layout_constraintBottom_toBottomOf="@id/tor_icon" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/tor_icon" + app:layout_constraintHorizontal_bias="0" + android:paddingHorizontal="@dimen/stdpadding" + android:gravity="bottom" + tools:text="test 12321 123 \n sdf,sdf,m\nn 123 " + android:ellipsize="end" + + tools:visibility="visible" + /> + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_snowflake_status" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/tor_icon" + app:layout_constraintEnd_toEndOf="parent" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:text="@string/snowflake_status" + android:textStyle="bold" + android:paddingTop="@dimen/stdpadding" + android:paddingHorizontal="@dimen/stdpadding"/> + + <androidx.appcompat.widget.AppCompatImageView + android:id="@+id/snowflake_icon" + android:layout_width="35dp" + android:layout_height="35dp" + android:src="@drawable/ic_snowflake" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/tv_snowflake_status" + android:layout_alignParentStart="true" + android:layout_marginBottom="@dimen/stdpadding" + android:padding="4dp" + /> + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/snowflake_state" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintStart_toEndOf="@id/snowflake_icon" + app:layout_constraintTop_toTopOf="@id/snowflake_icon" + app:layout_constraintBottom_toBottomOf="@id/snowflake_icon" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + android:paddingBottom="1dp" + android:paddingHorizontal="@dimen/stdpadding" + android:fadingEdge="horizontal" + android:maxLines="2" + android:text="@string/configuring_provider" + android:textAppearance="@style/Base.TextAppearance.AppCompat.Small" + + android:gravity="bottom" + tools:text="test \n another \n and a third \n blkud" + android:ellipsize="end" + tools:visibility="visible" + /> + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_logs" + app:layout_constraintTop_toBottomOf="@id/snowflake_state" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/log_fragment_title" + android:textStyle="bold" + android:paddingTop="@dimen/stdpadding" + android:paddingHorizontal="@dimen/stdpadding"/> + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/connection_detail_logs" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/tv_logs" + app:layout_constraintHeight_max="180dp" + android:layout_below="@+id/tv_logs" + android:layout_width="0dp" + android:layout_height="wrap_content" + tools:listitem="@layout/v_log_item" + android:isScrollContainer="false" + /> + </androidx.constraintlayout.widget.ConstraintLayout> + </androidx.appcompat.widget.LinearLayoutCompat> + </androidx.appcompat.widget.LinearLayoutCompat> + </androidx.cardview.widget.CardView> + + + </androidx.constraintlayout.widget.ConstraintLayout> +</androidx.core.widget.NestedScrollView>
\ No newline at end of file diff --git a/app/src/main/res/layout/f_empty_permission_setup.xml b/app/src/main/res/layout/f_empty_permission_setup.xml new file mode 100644 index 00000000..0fbb55e4 --- /dev/null +++ b/app/src/main/res/layout/f_empty_permission_setup.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="@dimen/stdpadding" + android:layout_margin="@dimen/activity_margin" />
\ No newline at end of file diff --git a/app/src/main/res/layout/f_notification_setup.xml b/app/src/main/res/layout/f_notification_setup.xml new file mode 100644 index 00000000..d9c7d1a3 --- /dev/null +++ b/app/src/main/res/layout/f_notification_setup.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> + + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="@dimen/stdpadding" + android:layout_margin="@dimen/activity_margin" + tools:context=".providersetup.fragments.ProviderSelectionFragment"> + + <androidx.appcompat.widget.LinearLayoutCompat + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_margin="@dimen/activity_margin" + > + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.AppCompat.Title" + android:text="@string/title_upcoming_notifications_request" + android:paddingBottom="@dimen/stdpadding" + /> + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_circumvention_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.AppCompat.Body1" + android:text="@string/upcoming_notifications_request_description"/> + </androidx.appcompat.widget.LinearLayoutCompat> +</ScrollView>
\ No newline at end of file diff --git a/app/src/main/res/layout/f_provider_selection.xml b/app/src/main/res/layout/f_provider_selection.xml new file mode 100644 index 00000000..7c861a14 --- /dev/null +++ b/app/src/main/res/layout/f_provider_selection.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="utf-8"?> + + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="@dimen/stdpadding" + android:layout_margin="@dimen/activity_margin" + tools:context=".providersetup.fragments.ProviderSelectionFragment"> + + <androidx.appcompat.widget.LinearLayoutCompat + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_margin="@dimen/activity_margin" + > + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_welcome" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.AppCompat.Title" + android:text="@string/welcome" + android:layout_alignParentTop="true" + /> + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_choose_provider" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.AppCompat.Title" + android:text="@string/select_provider" + android:paddingBottom="@dimen/stdpadding" + /> + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_provider_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.AppCompat.Body1" + android:text="@string/select_provider_description"/> + + <androidx.cardview.widget.CardView + android:id="@+id/cv_provider_selection_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:cardElevation="2dp" + app:cardUseCompatPadding="true" + app:cardCornerRadius="12dp" + android:layout_marginTop="40dp" + > + <androidx.appcompat.widget.LinearLayoutCompat + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:background="@color/colorPrimary_transparent" + android:padding="@dimen/activity_horizontal_margin" + > + <RadioGroup + android:id="@+id/provider_radioGroup" + android:layout_width="match_parent" + android:layout_height="wrap_content" + > + </RadioGroup> + <androidx.appcompat.widget.LinearLayoutCompat + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:id="@+id/expandable_detail_container"> + <androidx.appcompat.widget.AppCompatTextView + android:paddingTop="@dimen/stdpadding" + android:id="@+id/provider_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:text="@string/provider_description_riseup"/> + <androidx.appcompat.widget.AppCompatEditText + android:id="@+id/edit_customProvider" + android:layout_marginVertical="@dimen/stdpadding" + android:paddingHorizontal="@dimen/stdpadding" + android:paddingVertical="@dimen/compact_padding" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/white" + android:hint="https://example.org" + android:textAppearance="@style/TextAppearance.AppCompat.Body1" + android:textColorHint="@color/black800_transparent" + /> + </androidx.appcompat.widget.LinearLayoutCompat> + </androidx.appcompat.widget.LinearLayoutCompat> + </androidx.cardview.widget.CardView> + </androidx.appcompat.widget.LinearLayoutCompat> + +</ScrollView>
\ No newline at end of file diff --git a/app/src/main/res/layout/f_setup_success.xml b/app/src/main/res/layout/f_setup_success.xml new file mode 100644 index 00000000..94d3c24d --- /dev/null +++ b/app/src/main/res/layout/f_setup_success.xml @@ -0,0 +1,82 @@ +<?xml version="1.0" encoding="utf-8"?> + + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".providersetup.fragments.ProviderSelectionFragment"> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/top_button_guideline" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.5" + /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_vertical_left" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.3" + /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/guideline_vertical_right" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.7" + /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/bottom_button_guideline" + android:layout_width="0dp" + android:layout_height="0dp" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.8" + /> + + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="@dimen/stdpadding" + android:layout_margin="@dimen/activity_margin" + app:layout_constraintTop_toTopOf="parent" + > + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.AppCompat.Title" + android:text="@string/title_setup_success" + android:paddingBottom="@dimen/stdpadding" + /> + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_setup_success_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintTop_toBottomOf="@id/tv_title" + android:textAppearance="@style/TextAppearance.AppCompat.Body1" + android:text="@string/setup_success_description"/> + </LinearLayout> + + <se.leap.bitmaskclient.base.views.MainButton + android:id="@+id/main_button" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintTop_toBottomOf="@id/top_button_guideline" + app:layout_constraintBottom_toTopOf="@id/bottom_button_guideline" + app:layout_constraintDimensionRatio="1:1" + app:layout_constraintEnd_toStartOf="@+id/guideline_vertical_right" + app:layout_constraintStart_toStartOf="@+id/guideline_vertical_left" + /> + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/f_vpn_permission_setup.xml b/app/src/main/res/layout/f_vpn_permission_setup.xml new file mode 100644 index 00000000..99dd531b --- /dev/null +++ b/app/src/main/res/layout/f_vpn_permission_setup.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="utf-8"?> + + +<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:padding="@dimen/stdpadding" + android:layout_margin="@dimen/activity_margin" + tools:context=".providersetup.fragments.ProviderSelectionFragment"> + + <androidx.appcompat.widget.LinearLayoutCompat + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:layout_margin="@dimen/activity_margin" + > + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.AppCompat.Title" + android:text="@string/title_upcoming_connection_request" + android:paddingBottom="@dimen/stdpadding" + /> + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_circumvention_description" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAppearance="@style/TextAppearance.AppCompat.Body1" + android:text="@string/upcoming_connection_request_description"/> + </androidx.appcompat.widget.LinearLayoutCompat> +</ScrollView>
\ No newline at end of file diff --git a/app/src/main/res/layout/v_progress_spinner.xml b/app/src/main/res/layout/v_progress_spinner.xml new file mode 100644 index 00000000..ad106fb2 --- /dev/null +++ b/app/src/main/res/layout/v_progress_spinner.xml @@ -0,0 +1,128 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> + + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/vpn_btn_guideline_left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.125" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/vpn_btn_guideline_right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.875" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/vpn_btn_guideline_top" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.125" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/vpn_btn_guideline_bottom" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.875" /> + + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/icn_guideline_left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.3" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/icn_guideline_right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.7" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/icn_guideline_top" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.3" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/icn_guideline_bottom" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.7" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/border_guideline_left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.025" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/border_guideline_right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + app:layout_constraintGuide_percent="0.975" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/border_guideline_top" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.025" /> + + <androidx.constraintlayout.widget.Guideline + android:id="@+id/border_guideline_bottom" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + app:layout_constraintGuide_percent="0.975" /> + + <androidx.appcompat.widget.AppCompatImageView + android:id="@+id/spinner_view" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toTopOf="@+id/vpn_btn_guideline_bottom" + app:layout_constraintEnd_toStartOf="@+id/vpn_btn_guideline_right" + app:layout_constraintStart_toStartOf="@+id/vpn_btn_guideline_left" + app:layout_constraintTop_toTopOf="@+id/vpn_btn_guideline_top" + app:layout_constraintDimensionRatio="1:1" + app:srcCompat="@drawable/progress_spinner" + /> + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/tv_progress" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toTopOf="@+id/icn_guideline_bottom" + app:layout_constraintEnd_toStartOf="@+id/icn_guideline_right" + app:layout_constraintStart_toStartOf="@+id/icn_guideline_left" + app:layout_constraintTop_toTopOf="@+id/icn_guideline_top" + tools:text="100%" + android:maxLines="1" + android:gravity="center" + android:textStyle="bold" + app:autoSizeTextType="uniform" + app:autoSizeMinTextSize="12sp" + app:autoSizeMaxTextSize="100sp" + app:autoSizeStepGranularity="2sp" /> + /> + + + + + + +</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/app/src/main/res/values-port/dimens.xml b/app/src/main/res/values-port/dimens.xml index 90b6b82b..55344e51 100644 --- a/app/src/main/res/values-port/dimens.xml +++ b/app/src/main/res/values-port/dimens.xml @@ -1,5 +1,3 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <dimen name="footer_text_padding">20dp</dimen> - <dimen name="splash_text_top_padding">120dp</dimen> </resources>
\ No newline at end of file diff --git a/app/src/main/res/values-sw600dp-port/dimens.xml b/app/src/main/res/values-sw600dp-port/dimens.xml index b6315052..55344e51 100644 --- a/app/src/main/res/values-sw600dp-port/dimens.xml +++ b/app/src/main/res/values-sw600dp-port/dimens.xml @@ -1,7 +1,3 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - - <dimen name="footer_text_padding">20dp</dimen> - <dimen name="splash_text_top_padding">240dp</dimen> - </resources>
\ No newline at end of file diff --git a/app/src/main/res/values-sw600dp/dimens.xml b/app/src/main/res/values-sw600dp/dimens.xml index 4c726da5..fb8e7ded 100644 --- a/app/src/main/res/values-sw600dp/dimens.xml +++ b/app/src/main/res/values-sw600dp/dimens.xml @@ -12,9 +12,8 @@ <dimen name="compact_padding">6dp</dimen> <dimen name="button_radius">20dp</dimen> <!-- landscape dimens --> - <dimen name="footer_text_padding">20dp</dimen> - <dimen name="splash_text_top_padding">140dp</dimen> <dimen name="mainbutton_padding">32dp</dimen> <dimen name="donation_reminder_padding">24dp</dimen> + <dimen name="setup_bottom_bar">75dp</dimen> </resources>
\ No newline at end of file diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml index 822259ca..cfbfc6a6 100644 --- a/app/src/main/res/values-w820dp/dimens.xml +++ b/app/src/main/res/values-w820dp/dimens.xml @@ -9,8 +9,6 @@ <dimen name="stdpadding">16dp</dimen> <dimen name="button_radius">20dp</dimen> <!-- landscape dimens --> - <dimen name="footer_text_padding">12dp</dimen> - <dimen name="splash_text_top_padding">90dp</dimen> <dimen name="mainbutton_padding">32dp</dimen> <dimen name="donation_reminder_padding">24dp</dimen> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 0189bacf..10d802cd 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,14 +1,13 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <color name="colorPrimary">#b39ddb</color> - <color name="colorPrimaryDark">#ac97d2</color> - <color name="colorPrimary_transparent">#20000000</color> + <color name="colorPrimary">#ff1b8b</color> + <color name="colorPrimaryLight">#FF69B4</color> + <color name="colorPrimaryDark">#ef0072</color> + <color name="colorPrimary_transparent">#0B000000</color> <color name="colorBackground">#fffafafa</color> <color name="colorError">#ef9a9a</color> <color name="colorSuccess">#a5d6a7</color> <color name="colorDisabled">#AAAAAA</color> - <color name="colorMainBtnHighlight">#03DAC6</color> - <color name="colorMainBtnError">#eF2222</color> <color name="black">#000000</color> <color name="black800_dark">#1b1b1b</color> @@ -43,8 +42,8 @@ <!-- button text colors in Alerts etc. --> <color name="color_font_btn">@color/black800</color> <!-- pill style button text colors --> - <color name="color_font_btn_primary">@color/black800</color> - <color name="color_font_btn_secondary">@color/black800</color> + <color name="color_font_btn_primary">@color/white</color> + <color name="color_font_btn_secondary">@color/white</color> <color name="colorLocationButtonTint">@color/black</color> <color name="colorLocationButtonTintTransparent">@color/black800_high_transparent</color> <color name="colorWarning">#B33A3A</color> @@ -67,6 +66,8 @@ <color name="bg_running">#CCDCB8</color> <!-- actionbar and status bar colors for different connection states --> + <color name="bg_setup_action_bar">@color/colorPrimary</color> + <color name="bg_setup_status_bar">@color/colorPrimaryDark</color> <color name="bg_disconnected_top">#EC6767</color> <color name="bg_disconnected_top_light_transparent">#CCff9895</color> <color name="bg_connecting_top">#FADD85</color> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index f15fbcf9..b17683ae 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -9,9 +9,7 @@ <dimen name="compact_padding">3dp</dimen> <dimen name="standard_margin">8dp</dimen> <bool name="logSildersAlwaysVisible">false</bool> - <!-- landscape layout dimens --> - <dimen name="footer_text_padding">12dp</dimen> - <dimen name="splash_text_top_padding">80dp</dimen> + <dimen name="diameter">48dp</dimen> <dimen name="elevation_low">1dp</dimen> @@ -48,4 +46,5 @@ <dimen name="mainbutton_padding">8dp</dimen> <dimen name="donation_reminder_padding">8dp</dimen> + <dimen name="setup_bottom_bar">50dp</dimen> </resources>
\ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cb9a843b..90d06333 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,209 +1,240 @@ <?xml version='1.0' encoding='UTF-8'?> <resources> - <string name="retry">Retry</string> - <string name="repository_url_text">Source code available at https://0xacab.org/leap/bitmask_android</string> - <string name="leap_tracker">Issue tracker available at https://0xacab.org/leap/bitmask_android/issues</string> - <string name="translation_project_text">Translations welcome and appreciated. See our Transifex project at https://www.transifex.com/projects/p/bitmask/</string> - <string name="switch_provider_menu_option">Switch provider</string> - <string name="info">info</string> - <string name="show_connection_details">Show connection details</string> - <string name="connection_details">Connection details</string> - <string name="routes_info">Routes: %s</string> - <string name="routes_info6">IPv6 routes: %s</string> - <string name="error_empty_username">The username must not be empty.</string> - <string name="cert_from_keystore">Got certificate \'%s\' from keystore</string> - <string name="provider_label">Provider:</string> - <string name="provider_label_none">No provider configured</string> - <string name="status_unknown">Status unknown.</string> - <string name="eip_service_label">Encrypted VPN Internet Access</string> - <string name="configuration_wizard_title">Select a service provider</string> - <string name="add_provider">Add new Provider</string> - <string name="introduce_new_provider">Add a new service provider</string> - <string name="save">Save</string> - <string name="new_provider_uri">Domain name</string> - <string name="valid_url_entered">The URL is valid</string> - <string name="not_valid_url_entered">Malformed URL</string> - <string name="provider_details_title">Provider details</string> - <string name="use_anonymously_button">Use anonymously</string> - <string name="username_hint">username</string> - <string name="username_ask">Please enter your username</string> - <string name="password_ask">Please enter your password</string> - <string name="password_hint">password</string> - <string name="password_match">Passwords match</string> - <string name="password_mismatch">Passwords do not match</string> - <string name="user_message">User message</string> - <string name="about_fragment_title">About</string> - <string name="exclude_apps_fragment_title">Exclude apps from VPN</string> - <string name="error_srp_math_error_user_message">Try again: Server math error</string> - <string name="error_bad_user_password_user_message">Incorrect username or password</string> - <string name="error_not_valid_password_user_message">It must be at least 8 characters long</string> - <string name="error_client_http_user_message">Try again: Client HTTP error</string> - <string name="error_io_exception_user_message">Try again: I/O error</string> - <string name="error_json_exception_user_message">Try again: Bad response from the server</string> - <string name="error_no_such_algorithm_exception_user_message">Encryption algorithm not found. Please upgrade Android!</string> - <string name="signup_or_login_button">Sign Up/Log In</string> - <string name="login_button">Log In</string> - <string name="login_to_profile">Log in to profile</string> - <string name="logout_button">Log Out</string> - <string name="signup_button">Sign Up</string> - <string name="create_profile">Create profile</string> - <string name="setup_provider">Set up provider</string> - <string name="setup_error_title">Configuration Error</string> - <string name="setup_error_configure_button">Configure</string> - <string name="setup_error_close_button">Exit</string> - <string name="setup_error_text">There was an error configuring %s with your chosen provider.\n\nYou may choose to reconfigure, or exit and configure a provider upon next launch.</string> - <string name="setup_error_text_custom">There was an error configuring %s.\n\nYou may choose to reconfigure, or exit.</string> - <string name="server_unreachable_message">The server is unreachable, please try again.</string> - <string name="error.security.pinnedcertificate">Security error, upgrade the app or choose another provider.</string> - <string name="malformed_url">It doesn\'t seem to be a %s provider.</string> - <string name="certificate_error">This is not a trusted %s provider.</string> - <string name="service_is_down_error">The service is down.</string> - <string name="configuring_provider">Configuring provider</string> - <string name="incorrectly_downloaded_certificate_message">Your anonymous certificate was not downloaded</string> - <string name="downloading_certificate_message">Downloading VPN certificate</string> - <string name="updating_certificate_message">Updating VPN certificate</string> - <string name="login.riseup.warning">Riseup users will need to create a separate account to use the VPN service</string> - <string name="succesful_authentication_message">Authenticated</string> - <string name="authentication_failed_message">Authentication failed</string> - <string name="registration_failed_message">Registration failed</string> - <string name="eip_status_start_pending">Initiating connection</string> - <string name="eip_status_connecting">Connecting VPN</string> - <string name="eip_status_unsecured">Unsecured Connection</string> - <string name="eip_status_secured">Secured Connection</string> - <string name="eip_cancel_connect_title">Cancel connection?</string> - <string name="eip_cancel_connect_text">There is a connection attempt in progress. Do you wish to cancel it?</string> - <string name="eip.warning.browser_inconsistency">Turn off VPN connection? When the VPN is off, you may leak personal information to your Internet provider or local network.</string> - <string name="eip_state_not_connected">Not running! Insecure connection!</string> - <string name="eip_state_connected">Connection Secure</string> - <string name="provider_problem">It seems there is a problem with the provider.</string> - <string name="try_another_provider">Please try another provider, or contact yours.</string> - <string name="default_username">Anonymous</string> - <string name="logging_in">Logging in</string> - <string name="signing_up">Signing up</string> - <string name="vpn.button.turn.on">Turn on</string> - <string name="vpn.button.turn.off">Turn off</string> - <string name="vpn_button_turn_off_blocking">Stop blocking</string> - <string name="vpn_securely_routed">Your traffic is securely routed through:</string> - <string name="vpn_securely_routed_no_internet">No internet connection detected, when it comes back we\'ll route your traffic securely through:</string> - <string name="log_fragment_title">Log</string> - <string name="vpn_fragment_title">VPN</string> - <string name="navigation_drawer_open">Open navigation drawer</string> - <string name="navigation_drawer_close">Close navigation drawer</string> - <string name="action_example">Example action</string> - <string name="action_settings">Settings</string> - <string name="void_vpn_establish">%s blocks all outgoing internet traffic.</string> - <string name="void_vpn_error_establish">Blocking all internet traffic failed.</string> - <string name="void_vpn_stopped">Stopped blocking all outgoing internet traffic.</string> - <string name="void_vpn_title">Blocking traffic</string> - <string name="update_provider_details">Update provider details</string> - <string name="update_certificate">Update certificate</string> - <string name="warning_eip_json_corrupted">Updating provider configuration failed.</string> - <string name="eip_json_corrupted_user_message">Updating provider configuration failed. Please log in to try again.</string> - <string name="warning_client_parsing_error_gateways">The provider gateways could not be recognized. They may be configured incorrectly.</string> - <string name="warning_corrupted_provider_details">Stored provider details are corrupted. You can either update %s (recommended) or update the provider details using a commercial CA certificate.</string> - <string name="warning_corrupted_provider_cert">Stored provider certificate is invalid. You can either update %s (recommended) or update the provider certificate using a commercial CA certificate.</string> - <string name="warning_expired_provider_cert">Stored provider certificate is expired. You can either update %s (recommended) or update the provider certificate using a commercial CA certificate.</string> - <string name="downloading_vpn_certificate_failed">Downloading the VPN certificate failed. Try again or choose another provider.</string> - <string name="vpn_certificate_is_invalid">VPN certificate is invalid. Try to download a new one.</string> - <string name="vpn_certificate_user_message">The VPN certificate is invalid. Please log in to download a new one.</string> - <string name="save_battery">Save battery</string> - <string name="subtitle_save_battery">Disabled while VPN Hotspot is on</string> - <string name="save_battery_message">Background data connections will hibernate when your phone is inactive.</string> - <string name="always_on_vpn">Always-on VPN</string> - <string name="subtitle_always_on_vpn">Open Android System Settings</string> - <string name="tethering">VPN Hotspot</string> - <string name="ipv6Firewall">Block IPv6</string> - <string name="require_root">Requires root permissions</string> - <string name="show_experimental">Show experimental features</string> - <string name="hide_experimental">Hide experimental features</string> - <string name="experimental_features">Experimental features</string> - <string name="tethering_enabled_message">Please make sure to enable tethering in the <![CDATA[<b>system settings</b>]]> first.</string> - <string name="tethering_message">Share your VPN with other devices via:</string> - <string name="tethering_wifi">Wi-Fi hotspot</string> - <string name="tethering_usb">USB tethering</string> - <string name="tethering_bluetooth">Bluetooth tethering</string> - <string name="do_not_show_again">Do not show again</string> - <string name="always_on_vpn_user_message">To enable always-on VPN in Android VPN Settings click on the configure icon [img src] and turn the switch on.</string> - <string name="always_on_blocking_vpn_user_message">To protect your privacy optimally, you should also activate the option \"Block connections without VPN\".</string> - <string name="donate_title">Donate</string> - <string name="donate_default_message">Please donate today if you value secure communication that is easy for both the end-user and the service provider.</string> - <string name="donate_message">LEAP depends on donations and grants. Please donate today if you value secure communication that is easy for both the end-user and the service provider.</string> - <string name="donate_button_remind_later">Remind me later</string> - <string name="donate_button_donate">Donate</string> - <string name="obfuscated_connection">Using an obfuscated connection.</string> - <string name="obfuscated_connection_try">Trying an obfuscated connection.</string> - <string name="nav_drawer_obfuscated_connection">Use Bridges</string> - <string name="nav_drawer_subtitle_obfuscated_connection">Circumvent VPN filtering</string> - <string name="warning_exclude_apps_message">Be careful of excluding apps from VPN. This will reveal your identity and compromise your security.</string> - <plurals name="subtitle_exclude_apps"> - <item quantity="one">%d unprotected app</item> - <item quantity="other">%d unprotected apps</item> - </plurals> - <string name="warning_no_more_gateways_use_pt">%s could not connect. It might be that VPN connections get blocked. Do you want to try to connect using obfuscated connections?</string> - <string name="warning_no_more_gateways_no_pt">%s could not connect. Do you want to retry?</string> - <string name="warning_no_more_gateways_use_ovpn">%s could not connect using obfuscated VPN connections. Do you want to try to connect using a standard VPN?</string> - <string name="warning_no_more_gateways_manual_gw_selection">%1$s could not connect to %2$s. Do you want to try to connect automatically to the best location?</string> - <string name="warning_option_try_best">Try best location</string> - <string name="warning_option_try_pt">Try obfuscated connection</string> - <string name="warning_option_try_ovpn">Try standard connection</string> - <string name="vpn_error_establish">Android failed to establish the VPN service.</string> - <string name="root_permission_error">%s cannot execute features like VPN Hotspot or IPv6 firewall without root permissions.</string> - <string name="qs_enable_vpn">Start %s</string> - <string name="version_update_found">Tap here to start the download.</string> - <string name="version_update_title">A new %s version has been found.</string> - <string name="version_update_apk_description">Downloading a new %s version</string> - <string name="version_update_download_title">A new %s version has been downloaded.</string> - <string name="version_update_download_description">Tap here to install the update.</string> - <string name="version_update_error_pgp_verification">PGP verification error. Ignoring download.</string> - <string name="version_update_error">Update failed.</string> - <string name="version_update_error_permissions">No permissions to install app.</string> - <string name="gateway_selection_title">Select location</string> - <string name="gateway_selection_recommended_location">Recommended location</string> - <string name="gateway_selection_recommended">Recommended</string> - <string name="gateway_selection_manually">Manually select</string> - <string name="gateway_selection_automatic_location">Automatically use best connection</string> - <string name="gateway_selection_automatic">Automatic</string> - <string name="reconnecting">Reconnecting…</string> - <string name="tor_starting">Starting bridges for censorship circumvention…</string> - <string name="tor_stopping">Stopping bridges</string> - <string name="tor_started">Using bridges for censorship circumvention</string> - <string name="log_conn_done_pt">Connected to pluggable transport</string> - <string name="log_conn_pt">Connecting to pluggable transport</string> - <string name="log_conn_done">Connected to a relay</string> - <string name="log_handshake">Negotiating connection with a relay</string> - <string name="log_handshake_done">Connection with relay negotiated</string> - <string name="log_onehop_create">Establishing an encrypted directory connection</string> - <string name="log_requesting_status">Asking for network status consensus</string> - <string name="log_loading_status">Loading network status consensus</string> - <string name="log_loading_keys">Loading authority certificates</string> - <string name="log_requesting_descriptors">Asking for relay descriptors</string> - <string name="log_loading_descriptors">Loading relay descriptors</string> - <string name="log_enough_dirinfo">Loaded enough directory info to build circuits</string> - <string name="log_ap_handshake_done">Negotiation finished with a relay to build circuits</string> - <string name="log_circuit_create">Establishing a Tor circuit</string> - <string name="log_done">Running</string> - <string name="channel_name_tor_service">%s Bridges Service</string> - <string name="channel_description_tor_service">Informs about usage of bridges while configuring %s.</string> - <string name="error_tor_timeout">Starting bridges failed. Do you want to retry or continue with an unobfuscated secure connection to configure %s?</string> - <string name="retry_unobfuscated">Retry unobfuscated</string> - <string name="hide">Hide</string> - <string name="error_network_connection">%s has no internet connection. Please check your WiFi and cellular data settings.</string> - <string name="censorship_circumvention">Censorship circumvention</string> - <string name="use_snowflake">Use Snowflake</string> - <string name="snowflake_description">Protect configuration process against censorship.</string> - <string name="vpn_settings">VPN settings</string> - <string name="prefer_udp">Use UDP if available</string> - <string name="prefer_udp_subtitle">UDP can be faster and better for streaming, but does not work for all networks.</string> - <string name="disabled_while_bridges_on">Disabled while using bridges.</string> - <string name="hint_bridges">Only locations supporting bridges are currently selectable.</string> - <string name="option_disable_bridges">Disable bridges</string> - <string name="eip_state_insecure">Connection insecure</string> - <string name="connection_not_connected">You may be leaking information to your internet provider or local network.</string> - <string name="eip_state_no_network">You have no working Internet connection. Once you get it back, you will be automatically connected to</string> - <string name="eip_state_blocking">%1$s is blocking all internet traffic.</string> - <string name="disabled_while_udp_on">Disabled while UDP is on.</string> - <string name="advanced_settings">Advanced settings</string> - <string name="cancel_connection">Disconnect</string> - <string name="unknown_location">Unknown location</string> - <string name="splash_footer">Developed by LEAP</string> + <string name="retry">Retry</string> + <string name="repository_url_text">Source code available at https://0xacab.org/leap/bitmask_android</string> + <string name="leap_tracker">Issue tracker available at https://0xacab.org/leap/bitmask_android/issues</string> + <string name="translation_project_text">Translations welcome and appreciated. See our Transifex project at https://www.transifex.com/projects/p/bitmask/</string> + <string name="switch_provider_menu_option">Switch provider</string> + <string name="info">info</string> + <string name="show_connection_details">Show connection details</string> + <string name="connection_details">Connection details</string> + <string name="routes_info">Routes: %s</string> + <string name="routes_info6">IPv6 routes: %s</string> + <string name="error_empty_username">The username must not be empty.</string> + <string name="cert_from_keystore">Got certificate \'%s\' from keystore</string> + <string name="provider_label">Provider:</string> + <string name="provider_label_none">No provider configured</string> + <string name="status_unknown">Status unknown.</string> + <string name="eip_service_label">Encrypted VPN Internet Access</string> + <string name="configuration_wizard_title">Select a service provider</string> + <string name="add_provider">Add new Provider</string> + <string name="introduce_new_provider">Add a new service provider</string> + <string name="save">Save</string> + <string name="new_provider_uri">Domain name</string> + <string name="valid_url_entered">The URL is valid</string> + <string name="not_valid_url_entered">Malformed URL</string> + <string name="provider_details_title">Provider details</string> + <string name="use_anonymously_button">Use anonymously</string> + <string name="username_hint">username</string> + <string name="username_ask">Please enter your username</string> + <string name="password_ask">Please enter your password</string> + <string name="password_hint">password</string> + <string name="password_match">Passwords match</string> + <string name="password_mismatch">Passwords do not match</string> + <string name="user_message">User message</string> + <string name="about_fragment_title">About</string> + <string name="exclude_apps_fragment_title">Exclude apps from VPN</string> + <string name="error_srp_math_error_user_message">Try again: Server math error</string> + <string name="error_bad_user_password_user_message">Incorrect username or password</string> + <string name="error_not_valid_password_user_message">It must be at least 8 characters long</string> + <string name="error_client_http_user_message">Try again: Client HTTP error</string> + <string name="error_io_exception_user_message">Try again: I/O error</string> + <string name="error_json_exception_user_message">Try again: Bad response from the server</string> + <string name="error_no_such_algorithm_exception_user_message">Encryption algorithm not found. Please upgrade Android!</string> + <string name="signup_or_login_button">Sign Up/Log In</string> + <string name="login_button">Log In</string> + <string name="login_to_profile">Log in to profile</string> + <string name="logout_button">Log Out</string> + <string name="signup_button">Sign Up</string> + <string name="create_profile">Create profile</string> + <string name="setup_provider">Set up provider</string> + <string name="setup_error_title">Configuration Error</string> + <string name="setup_error_configure_button">Configure</string> + <string name="setup_error_close_button">Exit</string> + <string name="setup_error_text">There was an error configuring %s with your chosen provider.\n\nYou may choose to reconfigure, or exit and configure a provider upon next launch.</string> + <string name="setup_error_text_custom">There was an error configuring %s.\n\nYou may choose to reconfigure, or exit.</string> + <string name="server_unreachable_message">The server is unreachable, please try again.</string> + <string name="error.security.pinnedcertificate">Security error, upgrade the app or choose another provider.</string> + <string name="malformed_url">It doesn\'t seem to be a %s provider.</string> + <string name="certificate_error">This is not a trusted %s provider.</string> + <string name="service_is_down_error">The service is down.</string> + <string name="configuring_provider">Configuring provider</string> + <string name="incorrectly_downloaded_certificate_message">Your anonymous certificate was not downloaded</string> + <string name="downloading_certificate_message">Downloading VPN certificate</string> + <string name="updating_certificate_message">Updating VPN certificate</string> + <string name="login.riseup.warning">Riseup users will need to create a separate account to use the VPN service</string> + <string name="succesful_authentication_message">Authenticated</string> + <string name="authentication_failed_message">Authentication failed</string> + <string name="registration_failed_message">Registration failed</string> + <string name="eip_status_start_pending">Initiating connection</string> + <string name="eip_status_connecting">Connecting VPN</string> + <string name="eip_status_unsecured">Unsecured Connection</string> + <string name="eip_status_secured">Secured Connection</string> + <string name="eip_cancel_connect_title">Cancel connection?</string> + <string name="eip_cancel_connect_text">There is a connection attempt in progress. Do you wish to cancel it?</string> + <string name="eip.warning.browser_inconsistency">Turn off VPN connection? When the VPN is off, you may leak personal information to your Internet provider or local network.</string> + <string name="eip_state_not_connected">Not running! Insecure connection!</string> + <string name="eip_state_connected">Connection Secure</string> + <string name="provider_problem">It seems there is a problem with the provider.</string> + <string name="try_another_provider">Please try another provider, or contact yours.</string> + <string name="default_username">Anonymous</string> + <string name="logging_in">Logging in</string> + <string name="signing_up">Signing up</string> + <string name="vpn.button.turn.on">Turn on</string> + <string name="vpn.button.turn.off">Turn off</string> + <string name="vpn_button_turn_off_blocking">Stop blocking</string> + <string name="vpn_securely_routed">Your traffic is securely routed through:</string> + <string name="vpn_securely_routed_no_internet">No internet connection detected, when it comes back we\'ll route your traffic securely through:</string> + <string name="log_fragment_title">Log</string> + <string name="vpn_fragment_title">VPN</string> + <string name="navigation_drawer_open">Open navigation drawer</string> + <string name="navigation_drawer_close">Close navigation drawer</string> + <string name="action_example">Example action</string> + <string name="action_settings">Settings</string> + <string name="void_vpn_establish">%s blocks all outgoing internet traffic.</string> + <string name="void_vpn_error_establish">Blocking all internet traffic failed.</string> + <string name="void_vpn_stopped">Stopped blocking all outgoing internet traffic.</string> + <string name="void_vpn_title">Blocking traffic</string> + <string name="update_provider_details">Update provider details</string> + <string name="update_certificate">Update certificate</string> + <string name="warning_eip_json_corrupted">Updating provider configuration failed.</string> + <string name="eip_json_corrupted_user_message">Updating provider configuration failed. Please log in to try again.</string> + <string name="warning_client_parsing_error_gateways">The provider gateways could not be recognized. They may be configured incorrectly.</string> + <string name="warning_corrupted_provider_details">Stored provider details are corrupted. You can either update %s (recommended) or update the provider details using a commercial CA certificate.</string> + <string name="warning_corrupted_provider_cert">Stored provider certificate is invalid. You can either update %s (recommended) or update the provider certificate using a commercial CA certificate.</string> + <string name="warning_expired_provider_cert">Stored provider certificate is expired. You can either update %s (recommended) or update the provider certificate using a commercial CA certificate.</string> + <string name="downloading_vpn_certificate_failed">Downloading the VPN certificate failed. Try again or choose another provider.</string> + <string name="vpn_certificate_is_invalid">VPN certificate is invalid. Try to download a new one.</string> + <string name="vpn_certificate_user_message">The VPN certificate is invalid. Please log in to download a new one.</string> + <string name="save_battery">Save battery</string> + <string name="subtitle_save_battery">Disabled while VPN Hotspot is on</string> + <string name="save_battery_message">Background data connections will hibernate when your phone is inactive.</string> + <string name="always_on_vpn">Always-on VPN</string> + <string name="subtitle_always_on_vpn">Open Android System Settings</string> + <string name="tethering">VPN Hotspot</string> + <string name="ipv6Firewall">Block IPv6</string> + <string name="require_root">Requires root permissions</string> + <string name="show_experimental">Show experimental features</string> + <string name="hide_experimental">Hide experimental features</string> + <string name="experimental_features">Experimental features</string> + <string name="tethering_enabled_message">Please make sure to enable tethering in the <![CDATA[<b>system settings</b>]]> first.</string> + <string name="tethering_message">Share your VPN with other devices via:</string> + <string name="tethering_wifi">Wi-Fi hotspot</string> + <string name="tethering_usb">USB tethering</string> + <string name="tethering_bluetooth">Bluetooth tethering</string> + <string name="do_not_show_again">Do not show again</string> + <string name="always_on_vpn_user_message">To enable always-on VPN in Android VPN Settings click on the configure icon [img src] and turn the switch on.</string> + <string name="always_on_blocking_vpn_user_message">To protect your privacy optimally, you should also activate the option \"Block connections without VPN\".</string> + <string name="donate_title">Donate</string> + <string name="donate_default_message">Please donate today if you value secure communication that is easy for both the end-user and the service provider.</string> + <string name="donate_message">LEAP depends on donations and grants. Please donate today if you value secure communication that is easy for both the end-user and the service provider.</string> + <string name="donate_button_remind_later">Remind me later</string> + <string name="donate_button_donate">Donate</string> + <string name="obfuscated_connection">Using an obfuscated connection.</string> + <string name="obfuscated_connection_try">Trying an obfuscated connection.</string> + <string name="nav_drawer_obfuscated_connection">Use Bridges</string> + <string name="nav_drawer_subtitle_obfuscated_connection">Circumvent VPN filtering</string> + <string name="warning_exclude_apps_message">Be careful of excluding apps from VPN. This will reveal your identity and compromise your security.</string> + <plurals name="subtitle_exclude_apps"> + <item quantity="one">%d unprotected app</item> + <item quantity="other">%d unprotected apps</item> + </plurals> + <string name="warning_no_more_gateways_use_pt">%s could not connect. It might be that VPN connections get blocked. Do you want to try to connect using obfuscated connections?</string> + <string name="warning_no_more_gateways_no_pt">%s could not connect. Do you want to retry?</string> + <string name="warning_no_more_gateways_use_ovpn">%s could not connect using obfuscated VPN connections. Do you want to try to connect using a standard VPN?</string> + <string name="warning_no_more_gateways_manual_gw_selection">%1$s could not connect to %2$s. Do you want to try to connect automatically to the best location?</string> + <string name="warning_option_try_best">Try best location</string> + <string name="warning_option_try_pt">Try obfuscated connection</string> + <string name="warning_option_try_ovpn">Try standard connection</string> + <string name="vpn_error_establish">Android failed to establish the VPN service.</string> + <string name="root_permission_error">%s cannot execute features like VPN Hotspot or IPv6 firewall without root permissions.</string> + <string name="qs_enable_vpn">Start %s</string> + <string name="version_update_found">Tap here to start the download.</string> + <string name="version_update_title">A new %s version has been found.</string> + <string name="version_update_apk_description">Downloading a new %s version</string> + <string name="version_update_download_title">A new %s version has been downloaded.</string> + <string name="version_update_download_description">Tap here to install the update.</string> + <string name="version_update_error_pgp_verification">PGP verification error. Ignoring download.</string> + <string name="version_update_error">Update failed.</string> + <string name="version_update_error_permissions">No permissions to install app.</string> + <string name="gateway_selection_title">Select location</string> + <string name="gateway_selection_recommended_location">Recommended location</string> + <string name="gateway_selection_recommended">Recommended</string> + <string name="gateway_selection_manually">Manually select</string> + <string name="gateway_selection_automatic_location">Automatically use best connection</string> + <string name="gateway_selection_automatic">Automatic</string> + <string name="reconnecting">Reconnecting…</string> + <string name="tor_starting">Starting bridges for censorship circumvention…</string> + <string name="tor_stopping">Stopping bridges</string> + <string name="tor_started">Using bridges for censorship circumvention</string> + <string name="log_conn_done_pt">Connected to pluggable transport</string> + <string name="log_conn_pt">Connecting to pluggable transport</string> + <string name="log_conn_done">Connected to a relay</string> + <string name="log_handshake">Negotiating connection with a relay</string> + <string name="log_handshake_done">Connection with relay negotiated</string> + <string name="log_onehop_create">Establishing an encrypted directory connection</string> + <string name="log_requesting_status">Asking for network status consensus</string> + <string name="log_loading_status">Loading network status consensus</string> + <string name="log_loading_keys">Loading authority certificates</string> + <string name="log_requesting_descriptors">Asking for relay descriptors</string> + <string name="log_loading_descriptors">Loading relay descriptors</string> + <string name="log_enough_dirinfo">Loaded enough directory info to build circuits</string> + <string name="log_ap_handshake_done">Negotiation finished with a relay to build circuits</string> + <string name="log_circuit_create">Establishing a Tor circuit</string> + <string name="log_done">Running</string> + <string name="channel_name_tor_service">%s Bridges Service</string> + <string name="channel_description_tor_service">Informs about usage of bridges while configuring %s.</string> + <string name="error_tor_timeout">Starting bridges failed. Do you want to retry or continue with an unobfuscated secure connection to configure %s?</string> + <string name="retry_unobfuscated">Retry unobfuscated</string> + <string name="hide">Hide</string> + <string name="error_network_connection">%s has no internet connection. Please check your WiFi and cellular data settings.</string> + <string name="censorship_circumvention">Censorship circumvention</string> + <string name="use_snowflake">Use Snowflake</string> + <string name="snowflake_description">Protect configuration process against censorship.</string> + <string name="vpn_settings">VPN settings</string> + <string name="prefer_udp">Use UDP if available</string> + <string name="prefer_udp_subtitle">UDP can be faster and better for streaming, but does not work for all networks.</string> + <string name="disabled_while_bridges_on">Disabled while using bridges.</string> + <string name="hint_bridges">Only locations supporting bridges are currently selectable.</string> + <string name="option_disable_bridges">Disable bridges</string> + <string name="eip_state_insecure">Connection insecure</string> + <string name="connection_not_connected">You may be leaking information to your internet provider or local network.</string> + <string name="eip_state_no_network">You have no working Internet connection. Once you get it back, you will be automatically connected to</string> + <string name="eip_state_blocking">%1$s is blocking all internet traffic.</string> + <string name="disabled_while_udp_on">Disabled while UDP is on.</string> + <string name="advanced_settings">Advanced settings</string> + <string name="cancel_connection">Disconnect</string> + <string name="unknown_location">Unknown location</string> + <string name="splash_footer">Developed by LEAP</string> + <string name="welcome">Welcome!</string> + <string name="select_provider">Select Your Provider</string> + <string name="select_provider_description">When using a VPN you are transferring your trust from your Internet Service Provider to your VPN provider. Bitmask only connects to providers with a clear history of privacy protection and advocacy.</string> + <string name="provider_description_riseup">Riseup provides online communication tools for people and groups working on liberatory social change. We are a project to create democratic alternatives and practice self-determination by controlling our own secure means of communications.</string> + <string name="next">Next</string> + <string name="add_provider_description">Bitmask connects to trusted providers that are not publicly listed. Enter your provider’s url below.</string> + <string name="provider_description_calyx">Calyx is a non-profit education and research organization devoted to studying, testing, developing and implementing privacy technology and tools to promote free speech, free expression, civic engagement and privacy rights on the internet and in the mobile communications industry.</string> + <string name="title_circumvention_setup">Do You Require Censorship Circumvention?</string> + <string name="circumvention_setup_description">If you live where the internet is censored you can use our censorship circumvention options to access all internet services. These options will slow down your connection!</string> + <string name="circumvention_setup_hint">Bitmask will automatically try to connect you to the internet using a variety of circumvention technologies. You can fine tune this in the advanced settings.</string> + <string name="use_standard_vpn">Use standard Bitmask VPN</string> + <string name="use_circumvention_tech">Use circumvention tech (slower)</string> + <string name="description_configure_provider">To connect to your provider Bitmask is fetching all the required configuration information. This only happens during first setup.</string> + <string name="description_configure_provider_circumvention">Bitmask is attempting to collect all required configuration data from the provider. This only happens during first setup. You selected to use circumvention technology, so this might take some time.</string> + <string name="percentage" translatable="false">%d%</string> + <string name="details">Details</string> + <string name="tor_status">Tor Status</string> + <string name="snowflake_status">Snowflake Status</string> + <string name="snowflake_started">Snowflake client started</string> + <string name="snowflake_negotiating_rendezvous_http">Negotiating Snowflake proxy rendezvous (http)</string> + <string name="snowflake_negotiating_rendezvous_amp_cache">Negotiating Snowflake proxy rendezvous (amp cache)</string> + <string name="snowflake_socks_error">Snowflake SOCKS error</string> + <string name="snowflake_broker_success">Snowflake proxy rendezvous successful</string> + <string name="snowflake_sending_data">Sending data via Snowflake</string> + <string name="title_upcoming_connection_request">Upcoming Connection Request</string> + <string name="upcoming_connection_request_description">In the next panel Android will remind you that it’s essential to trust your VPN provider. Bitmask only partners with providers that adhere to strict privacy best practices for VPNs and have a verifiable history of protecting user’s data and identities.</string> + <string name="title_upcoming_notifications_request">Upcoming Notifications Request</string> + <string name="upcoming_notifications_request_description">In the next panel Android will ask if you want to allow notifications. This will ensure a stable background connection and enable you to see your data usage from within Android’s notification center.</string> + <string name="title_setup_success">You\'re all set!</string> + <string name="setup_success_description">Click the button below to connect</string> + </resources> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index f42be3c1..8ee40614 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -4,7 +4,7 @@ ~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt --> -<resources> +<resources xmlns:tools="http://schemas.android.com/tools"> <style name="blinkt.baseTheme" parent="android:Theme.DeviceDefault.Light" /> <style name="blinkt" parent="blinkt.baseTheme" /> @@ -68,6 +68,16 @@ <item name="android:textColor">@color/color_font_btn</item> </style> + <style name="BitmaskSetupButton" parent="Widget.AppCompat.Button"> + <item name="android:background">@drawable/cust_setup_button_primary</item> + <item name="android:textAllCaps">false</item> + <item name="android:height">36dp</item> + <item name="android:minWidth">75dp</item> + <item name="android:textColor">@color/colorActionBarTitleFont</item> + <item name="android:textStyle">bold</item> + <item name="android:letterSpacing">0.05</item> + </style> + <style name="BitmaskActivity"> </style> |