summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/src/main/AndroidManifest.xml47
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java3
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/utils/ViewHelper.java63
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/views/ProgressSpinner.java53
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderManager.java18
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/SetupViewPagerAdapter.java52
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/TorLogAdapter.java61
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ConfigWizardBaseActivity.java48
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivity.java138
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupInterface.java20
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java46
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java163
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java138
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java81
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModelFactory.java28
-rw-r--r--app/src/main/res/drawable-anydpi-v24/ic_arrow_forward.xml9
-rw-r--r--app/src/main/res/drawable/ic_arrow_forward.pngbin0 -> 462 bytes
-rw-r--r--app/src/main/res/drawable/progress_spinner.xml28
-rw-r--r--app/src/main/res/layout/activity_setup.xml118
-rw-r--r--app/src/main/res/layout/f_circumvention_setup.xml76
-rw-r--r--app/src/main/res/layout/f_configure_provider.xml188
-rw-r--r--app/src/main/res/layout/f_provider_selection.xml91
-rw-r--r--app/src/main/res/layout/v_progress_spinner.xml128
-rw-r--r--app/src/main/res/values/strings.xml431
24 files changed, 1737 insertions, 291 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index cbc06854..a9971904 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,24 +1,8 @@
<?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" />
@@ -26,9 +10,9 @@
<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 +26,10 @@
android:appCategory="productivity"
android:logo="@mipmap/ic_launcher"
android:theme="@style/BitmaskTheme">
+ <activity
+ android:name=".providersetup.activities.SetupActivity"
+ android:exported="false" />
+
<service
android:name="de.blinkt.openvpn.core.OpenVPNService"
android:exported="false"
@@ -67,11 +55,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 +70,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 +85,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 +126,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/se/leap/bitmaskclient/base/StartActivity.java b/app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java
index 94000a0f..4748b22e 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/StartActivity.java
@@ -56,6 +56,7 @@ 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
@@ -249,7 +250,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);
}
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..efc6d093 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,5 +1,10 @@
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;
@@ -16,10 +21,12 @@ import android.view.WindowManager;
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.appcompat.widget.LinearLayoutCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
@@ -146,4 +153,60 @@ 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();
+ }
+
}
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..b0b81624
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/base/views/ProgressSpinner.java
@@ -0,0 +1,53 @@
+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) {
+ textView.setText(textView.getContext().getString(R.string.percentage, progress));
+ }
+}
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/SetupViewPagerAdapter.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/SetupViewPagerAdapter.java
new file mode 100644
index 00000000..d32a4eb6
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/SetupViewPagerAdapter.java
@@ -0,0 +1,52 @@
+package se.leap.bitmaskclient.providersetup;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.Lifecycle;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+
+import se.leap.bitmaskclient.providersetup.fragments.CircumventionSetupFragment;
+import se.leap.bitmaskclient.providersetup.fragments.ConfigureProviderFragment;
+import se.leap.bitmaskclient.providersetup.fragments.ProviderSelectionFragment;
+
+public class SetupViewPagerAdapter extends FragmentStateAdapter {
+
+
+ public SetupViewPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
+ super(fragmentActivity);
+ }
+
+ public SetupViewPagerAdapter(@NonNull Fragment fragment) {
+ super(fragment);
+ }
+
+ public SetupViewPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
+ super(fragmentManager, lifecycle);
+ }
+
+ @NonNull
+ @Override
+ public Fragment createFragment(int position) {
+ switch (position) {
+ case 0:
+ return ProviderSelectionFragment.newInstance();
+ case 1:
+ return CircumventionSetupFragment.newInstance();
+ case 2:
+ return ConfigureProviderFragment.newInstance(position);
+ default:
+ return ProviderSelectionFragment.newInstance();
+ }
+ }
+
+
+
+ @Override
+ public int getItemCount() {
+ return 4;
+ }
+
+
+}
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/ConfigWizardBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ConfigWizardBaseActivity.java
index caba1436..fdc482fd 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
@@ -15,7 +15,6 @@ 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;
@@ -42,6 +41,7 @@ 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;
/**
@@ -431,50 +431,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..f62f959d
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivity.java
@@ -0,0 +1,138 @@
+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 androidx.annotation.ColorInt;
+import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.content.ContextCompat;
+import androidx.viewpager2.widget.ViewPager2;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.Button;
+import android.widget.Toast;
+
+import se.leap.bitmaskclient.BuildConfig;
+import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.base.models.Provider;
+import se.leap.bitmaskclient.base.views.ActionBarTitle;
+import se.leap.bitmaskclient.databinding.ActivitySetupBinding;
+import se.leap.bitmaskclient.providersetup.SetupViewPagerAdapter;
+
+public class SetupActivity extends AppCompatActivity implements SetupInterface {
+
+ ActivitySetupBinding binding;
+ Provider provider;
+
+ @SuppressLint("ClickableViewAccessibility")
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = ActivitySetupBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+ SetupViewPagerAdapter adapter = new SetupViewPagerAdapter(getSupportFragmentManager(), getLifecycle());
+ View[] indicatorViews = {
+ binding.indicator1,
+ binding.indicator2,
+ binding.indicator3,
+ binding.indicator4,
+ binding.indicator5
+ };
+ binding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
+ @Override
+ public void onPageSelected(int position) {
+ super.onPageSelected(position);
+ for (int i = 0; i < indicatorViews.length; i++) {
+ indicatorViews[i].setBackgroundColor(ContextCompat.getColor(
+ SetupActivity.this,
+ i == position ? R.color.colorPrimaryDark : R.color.colorDisabled));
+ }
+ }
+ });
+ binding.viewPager.setAdapter(adapter);
+ binding.viewPager.setUserInputEnabled(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);
+ });
+ setupActionBar();
+ }
+
+ 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));
+ actionBarTitle.showSubtitle(false);
+
+ @ColorInt int titleColor = ContextCompat.getColor(context, R.color.colorActionBarTitleFont);
+ actionBarTitle.setTitleTextColor(titleColor);
+
+ actionBarTitle.setCentered(BuildConfig.actionbar_center_title);
+ 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);
+ }
+ }
+
+ public int getCurrentFragmentPosition() {
+ return binding.viewPager.getCurrentItem();
+ }
+
+
+ @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 setNavigationButtonHidden(boolean isHidden) {
+ binding.setupNextButton.setVisibility(isHidden ? GONE : VISIBLE);
+ }
+
+ @Override
+ public void onCanceled() {
+ binding.viewPager.setCurrentItem(0);
+ }
+
+ @Override
+ public void onProviderSelected(Provider provider) {
+ this.provider = provider;
+ }
+
+ @Override
+ public Provider getSelectedProvider() {
+ return provider;
+ }
+
+} \ No newline at end of file
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupInterface.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupInterface.java
new file mode 100644
index 00000000..1438ee5d
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupInterface.java
@@ -0,0 +1,20 @@
+package se.leap.bitmaskclient.providersetup.activities;
+
+import androidx.viewpager2.widget.ViewPager2;
+
+import se.leap.bitmaskclient.base.models.Provider;
+
+public interface SetupInterface {
+
+ void onSetupStepValidationChanged(boolean isValid);
+ void registerOnPageChangeCallback(ViewPager2.OnPageChangeCallback callback);
+ void removeOnPageChangeCallback(ViewPager2.OnPageChangeCallback callback);
+ void setNavigationButtonHidden(boolean isHidden);
+ void onCanceled();
+
+ void onProviderSelected(Provider provider);
+
+ Provider getSelectedProvider();
+
+}
+
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..606de943
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java
@@ -0,0 +1,46 @@
+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 androidx.fragment.app.Fragment;
+
+import se.leap.bitmaskclient.base.utils.PreferenceHelper;
+import se.leap.bitmaskclient.databinding.FCircumventionSetupBinding;
+
+public class CircumventionSetupFragment extends Fragment {
+
+ public static CircumventionSetupFragment newInstance() {
+ return new CircumventionSetupFragment();
+ }
+
+ @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(getContext(), true);
+ PreferenceHelper.useSnowflake(getContext(), true);
+ binding.tvCircumventionDetailDescription.setVisibility(View.VISIBLE);
+ binding.rbCircumvention.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
+ binding.rbPlainVpn.setTypeface(Typeface.DEFAULT, Typeface.NORMAL);
+ return;
+ }
+
+ PreferenceHelper.useBridges(getContext(), false);
+ PreferenceHelper.useSnowflake(getContext(), false);
+ binding.tvCircumventionDetailDescription.setVisibility(View.GONE);
+ binding.rbPlainVpn.setTypeface(Typeface.DEFAULT, Typeface.BOLD);
+ binding.rbCircumvention.setTypeface(Typeface.DEFAULT, Typeface.NORMAL);
+ });
+ binding.circumventionRadioGroup.check(binding.rbPlainVpn.getId());
+ return binding.getRoot();
+ }
+} \ 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..ceed2c3c
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java
@@ -0,0 +1,163 @@
+package se.leap.bitmaskclient.providersetup.fragments;
+
+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.base.utils.ViewHelper.animateContainerVisibility;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.SET_UP_PROVIDER;
+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.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 androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewpager2.widget.ViewPager2;
+
+import java.util.List;
+import java.util.Observable;
+import java.util.Observer;
+
+import se.leap.bitmaskclient.base.utils.PreferenceHelper;
+import se.leap.bitmaskclient.databinding.FConfigureProviderBinding;
+import se.leap.bitmaskclient.providersetup.ProviderAPICommand;
+import se.leap.bitmaskclient.providersetup.TorLogAdapter;
+import se.leap.bitmaskclient.providersetup.activities.SetupInterface;
+import se.leap.bitmaskclient.tor.TorStatusObservable;
+
+public class ConfigureProviderFragment extends Fragment implements Observer {
+
+ public static ConfigureProviderFragment newInstance(int position) {
+ return new ConfigureProviderFragment(position);
+ }
+
+ FConfigureProviderBinding binding;
+ private SetupInterface setupInterface;
+ private boolean isExpanded = false;
+ private final int position;
+ private ViewPager2.OnPageChangeCallback viewPagerCallback;
+ private TorLogAdapter torLogAdapter;
+
+
+ public ConfigureProviderFragment(int position) {
+ this.position = position;
+ }
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ binding = FConfigureProviderBinding.inflate(inflater, container, false);
+ binding.detailContainer.setVisibility(PreferenceHelper.getUseSnowflake(getContext()) ? VISIBLE : GONE);
+ binding.detailHeaderContainer.setOnClickListener(v -> {
+ binding.ivExpand.animate().setDuration(250).rotation(isExpanded ? 0 : 270);
+ showConnectionDetails();
+ animateContainerVisibility(binding.expandableDetailContainer, isExpanded);
+ isExpanded = !isExpanded;
+ });
+
+ binding.ivExpand.animate().setDuration(0).rotation(270);
+ return binding.getRoot();
+ }
+
+
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ binding = null;
+ }
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ setupInterface = (SetupInterface) getActivity();
+ viewPagerCallback = new ViewPager2.OnPageChangeCallback() {
+ @Override
+ public void onPageSelected(int position) {
+ super.onPageSelected(position);
+ if (position == ConfigureProviderFragment.this.position) {
+ binding.detailContainer.setVisibility(PreferenceHelper.getUseSnowflake(getContext()) ? VISIBLE : GONE);
+ setupInterface.setNavigationButtonHidden(true);
+ ProviderAPICommand.execute(context, SET_UP_PROVIDER, setupInterface.getSelectedProvider());
+ }
+ }
+ };
+ setupInterface.registerOnPageChangeCallback(viewPagerCallback);
+ TorStatusObservable.getInstance().addObserver(this);
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ TorStatusObservable.getInstance().deleteObserver(this);
+ setupInterface.removeOnPageChangeCallback(viewPagerCallback);
+ setupInterface = null;
+ }
+
+ protected void showConnectionDetails() {
+ LinearLayoutManager layoutManager = new LinearLayoutManager(this.getContext());
+ binding.connectionDetailLogs.setLayoutManager(layoutManager);
+ torLogAdapter = new TorLogAdapter(getLastLogs());
+ binding.connectionDetailLogs.setAdapter(torLogAdapter);
+
+ 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) {
+ return;
+ }
+ activity.runOnUiThread(() -> {
+ if (TorStatusObservable.getStatus() != TorStatusObservable.TorStatus.OFF) {
+ if (binding.connectionDetailContainer.getVisibility() == GONE) {
+ showConnectionDetails();
+ } else {
+ setLogs(getLastTorLog(), getLastSnowflakeLog(), getLastLogs());
+ }
+ }
+ 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);
+ }
+} \ 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..45ba73dc
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java
@@ -0,0 +1,138 @@
+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.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
+
+import java.util.ArrayList;
+
+import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.base.models.Provider;
+import se.leap.bitmaskclient.databinding.FProviderSelectionBinding;
+import se.leap.bitmaskclient.providersetup.activities.SetupInterface;
+import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModel;
+import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModelFactory;
+
+public class ProviderSelectionFragment extends Fragment {
+
+ private ProviderSelectionViewModel viewModel;
+ private ArrayList<RadioButton> radioButtons;
+ private SetupInterface setupCallback;
+
+ public static ProviderSelectionFragment newInstance() {
+ return new ProviderSelectionFragment();
+ }
+
+ @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) {
+ FProviderSelectionBinding binding = FProviderSelectionBinding.inflate(inflater, container, false);
+
+ radioButtons = new ArrayList<>();
+ for (int i = 0; i < viewModel.size(); i++) {
+ Provider provider = viewModel.getProvider(i);
+ RadioButton radioButton = new RadioButton(binding.getRoot().getContext());
+ radioButton.setText(provider.getDomain());
+ 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());
+ 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());
+ if (setupCallback != null) {
+ setupCallback.onSetupStepValidationChanged(viewModel.isValidConfig());
+ if (checkedId != ADD_PROVIDER) {
+ setupCallback.onProviderSelected(viewModel.getProvider(checkedId));
+ } else if (viewModel.isValidConfig()) {
+ setupCallback.onProviderSelected(new Provider(binding.editCustomProvider.getText().toString()));
+ }
+ }
+ });
+ binding.providerRadioGroup.check(viewModel.getSelected());
+
+ 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 (setupCallback == null) return;
+ setupCallback.onSetupStepValidationChanged(viewModel.isValidConfig());
+ if (viewModel.isValidConfig()) {
+ setupCallback.onProviderSelected(new Provider(s.toString()));
+ }
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {}
+ });
+ return binding.getRoot();
+ }
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ if (getActivity() instanceof SetupInterface) {
+ setupCallback = (SetupInterface) getActivity();
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ setupCallback = null;
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ radioButtons = null;
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ setupCallback.onSetupStepValidationChanged(viewModel.isValidConfig());
+ }
+} \ 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..e3880181
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/viewmodel/ProviderSelectionViewModel.java
@@ -0,0 +1,81 @@
+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 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;
+ }
+} \ 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/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/ic_arrow_forward.png b/app/src/main/res/drawable/ic_arrow_forward.png
new file mode 100644
index 00000000..2b45341c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_forward.png
Binary files differ
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/layout/activity_setup.xml b/app/src/main/res/layout/activity_setup.xml
new file mode 100644
index 00000000..fe302bb1
--- /dev/null
+++ b/app/src/main/res/layout/activity_setup.xml
@@ -0,0 +1,118 @@
+<?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"
+ 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="50dp"
+ 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:id="@+id/indicator1"
+ 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:id="@+id/indicator2"
+ 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:id="@+id/indicator3"
+ 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:id="@+id/indicator4"
+ 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:id="@+id/indicator5"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/colorPrimary"/>
+ </androidx.cardview.widget.CardView>
+
+ </LinearLayout>
+ <Button
+ 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:text="@string/next"
+ />
+ </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..dd865081
--- /dev/null
+++ b/app/src/main/res/layout/f_circumvention_setup.xml
@@ -0,0 +1,76 @@
+<?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:cardCornerRadius="12dp"
+ app:cardElevation="1dp"
+ android:layout_margin="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..6e272260
--- /dev/null
+++ b/app/src/main/res/layout/f_configure_provider.xml
@@ -0,0 +1,188 @@
+<?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_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/configuring_provider"
+ 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/description_configure_provider"/>
+
+ <se.leap.bitmaskclient.base.views.ProgressSpinner
+ android:id="@+id/progress_spinner"
+ android:layout_width="100dp"
+ android:layout_height="100dp" />
+
+ <androidx.cardview.widget.CardView
+ android:id="@+id/detail_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:cardCornerRadius="12dp"
+ app:cardElevation="1dp"
+ >
+ <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"
+ >
+ <RelativeLayout
+ 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"
+ 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"
+ android:layout_below="@id/tv_tor_status"
+ android:layout_alignParentStart="true"
+ android:padding="4dp"
+ android:src="@drawable/ic_tor" />
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/tor_state"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:fadingEdge="horizontal"
+ android:maxLines="2"
+ android:text="@string/configuring_provider"
+ android:textAppearance="@style/Base.TextAppearance.AppCompat.Small"
+ android:layout_alignBottom="@id/tor_icon"
+ android:layout_toEndOf="@id/tor_icon"
+ android:layout_alignParentEnd="true"
+ 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"
+ android:layout_below="@id/tor_state"
+ android:layout_width="match_parent"
+ 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"
+ android:layout_below="@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="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/tor_state"
+ android:fadingEdge="horizontal"
+ android:maxLines="2"
+ android:text="@string/configuring_provider"
+ android:textAppearance="@style/Base.TextAppearance.AppCompat.Small"
+ android:layout_alignTop="@id/snowflake_icon"
+ android:layout_alignBottom="@+id/snowflake_icon"
+ android:layout_toEndOf="@+id/snowflake_icon"
+ android:layout_alignParentEnd="true"
+ android:paddingBottom="1dp"
+ 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"
+ android:layout_below="@id/snowflake_state"
+ 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"
+ android:layout_below="@+id/tv_logs"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ tools:listitem="@layout/v_log_item"
+ android:isScrollContainer="false"
+ />
+
+ </RelativeLayout>
+ </androidx.appcompat.widget.LinearLayoutCompat>
+ </androidx.appcompat.widget.LinearLayoutCompat>
+ </androidx.cardview.widget.CardView>
+
+
+ </androidx.appcompat.widget.LinearLayoutCompat>
+</androidx.core.widget.NestedScrollView> \ 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..520e011a
--- /dev/null
+++ b/app/src/main/res/layout/f_provider_selection.xml
@@ -0,0 +1,91 @@
+<?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:cardCornerRadius="12dp"
+ app:cardElevation="1dp"
+ android:layout_margin="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/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/strings.xml b/app/src/main/res/values/strings.xml
index cb9a843b..d3550ac6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,209 +1,228 @@
<?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>
+
</resources>