summaryrefslogtreecommitdiff
path: root/app/src/main/java/se/leap/bitmaskclient
diff options
context:
space:
mode:
authorcyBerta <cyberta@riseup.net>2023-08-02 12:34:45 +0200
committercyBerta <cyberta@riseup.net>2023-08-02 12:34:45 +0200
commit0fa7ae499185fefa732a7bc02a8e22ea5da92ec7 (patch)
treeb58819fabfa2f90a54caa62f061a3314ecc22ebe /app/src/main/java/se/leap/bitmaskclient
parentdfe7a28d7a1fcabfdc6d493b29a558da9b0add46 (diff)
* Implenting permissionn fragments
* refactoring fragments, use of a base fragment to deduplicate code * improve SetupViewPagerAdapter by implementing a factory that hands out the reuired fragments in the correct order * very basic setup success fragment ("You're all set!")
Diffstat (limited to 'app/src/main/java/se/leap/bitmaskclient')
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/SetupViewPagerAdapter.java56
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivity.java82
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivityCallback.java4
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java63
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java18
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java68
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java92
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/NotificationSetupFragment.java37
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java63
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupFragmentFactory.java61
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java40
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/VpnPermissionSetupFragment.java44
12 files changed, 506 insertions, 122 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/SetupViewPagerAdapter.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/SetupViewPagerAdapter.java
index d32a4eb6..3f585c35 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/SetupViewPagerAdapter.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/SetupViewPagerAdapter.java
@@ -1,51 +1,57 @@
package se.leap.bitmaskclient.providersetup;
+import static se.leap.bitmaskclient.providersetup.fragments.SetupFragmentFactory.*;
+import static se.leap.bitmaskclient.providersetup.fragments.SetupFragmentFactory.CIRCUMVENTION_SETUP_FRAGMENT;
+import static se.leap.bitmaskclient.providersetup.fragments.SetupFragmentFactory.CONFIGURE_PROVIDER_FRAGMENT;
+import static se.leap.bitmaskclient.providersetup.fragments.SetupFragmentFactory.PROVIDER_SELECTION_FRAGMENT;
+
+import android.content.Intent;
+
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
-import androidx.fragment.app.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;
+import java.util.ArrayList;
-public class SetupViewPagerAdapter extends FragmentStateAdapter {
+import se.leap.bitmaskclient.providersetup.fragments.SetupFragmentFactory;
+public class SetupViewPagerAdapter extends FragmentStateAdapter {
- public SetupViewPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
- super(fragmentActivity);
- }
+ private SetupFragmentFactory setupFragmentFactory;
- public SetupViewPagerAdapter(@NonNull Fragment fragment) {
- super(fragment);
+ private SetupViewPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
+ super(fragmentManager, lifecycle);
}
- public SetupViewPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
- super(fragmentManager, lifecycle);
+ public SetupViewPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle, Intent vpnPermissionRequest, Boolean showNotificationPermission) {
+ this(fragmentManager, lifecycle);
+ ArrayList<Integer> fragments = new ArrayList<>();
+ fragments.add(PROVIDER_SELECTION_FRAGMENT);
+ fragments.add(CIRCUMVENTION_SETUP_FRAGMENT);
+ fragments.add(CONFIGURE_PROVIDER_FRAGMENT);
+ if (vpnPermissionRequest != null) {
+ fragments.add(VPN_PERMISSON_EDUCATIONAL_FRAGMENT);
+ fragments.add(VPN_PERMISSON_FRAGMENT);
+ }
+ if (showNotificationPermission) {
+ fragments.add(NOTIFICATION_PERMISSON_EDUCATIONAL_FRAGMENT);
+ fragments.add(NOTIFICATION_PERMISSON_FRAGMENT);
+ }
+ fragments.add(SUCCESS_FRAGMENT);
+ setupFragmentFactory = new SetupFragmentFactory(fragments, vpnPermissionRequest);
}
@NonNull
@Override
public Fragment createFragment(int position) {
- switch (position) {
- case 0:
- return ProviderSelectionFragment.newInstance();
- case 1:
- return CircumventionSetupFragment.newInstance();
- case 2:
- return ConfigureProviderFragment.newInstance(position);
- default:
- return ProviderSelectionFragment.newInstance();
- }
+ return setupFragmentFactory.createFragment(position);
}
-
-
@Override
public int getItemCount() {
- return 4;
+ return setupFragmentFactory.getItemCount();
}
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
index 33e9cbbd..3e91795b 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivity.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivity.java
@@ -4,6 +4,7 @@ import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static androidx.appcompat.app.ActionBar.DISPLAY_SHOW_CUSTOM;
+import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_CONFIGURE_LEAP;
import static se.leap.bitmaskclient.tor.TorStatusObservable.TorStatus.OFF;
import androidx.annotation.ColorInt;
@@ -12,16 +13,22 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.viewpager2.widget.ViewPager2;
+import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.VpnService;
+import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.Toast;
+import java.util.ArrayList;
import java.util.HashSet;
-import java.util.Iterator;
import se.leap.bitmaskclient.BuildConfig;
import se.leap.bitmaskclient.R;
@@ -45,22 +52,39 @@ public class SetupActivity extends AppCompatActivity implements SetupActivityCal
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
- };
+ ArrayList<View> indicatorViews = new ArrayList<>();
+
+ for (int i = 0; i < 4; i++) {
+ addIndicatorView(indicatorViews);
+ }
+
+ Intent requestVpnPermission = VpnService.prepare(this);
+ if (requestVpnPermission != null) {
+ addIndicatorView(indicatorViews);
+ addIndicatorView(indicatorViews);
+ }
+
+ boolean showNotificationPermissionFragments = false;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ContextCompat.checkSelfPermission(getApplication(), Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
+ showNotificationPermissionFragments = shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS);
+ if (showNotificationPermissionFragments) {
+ addIndicatorView(indicatorViews);
+ addIndicatorView(indicatorViews);
+ }
+ }
+ }
+
+ SetupViewPagerAdapter adapter = new SetupViewPagerAdapter(getSupportFragmentManager(), getLifecycle(), requestVpnPermission, showNotificationPermissionFragments);
+
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));
+ for (int i = 0; i < indicatorViews.size(); i++) {
+ ((ViewGroup) indicatorViews.get(i)).
+ getChildAt(0).
+ setBackgroundColor(ContextCompat.getColor(SetupActivity.this, (i == position) ? R.color.colorPrimaryDark : R.color.colorDisabled));
}
}
});
@@ -87,6 +111,20 @@ public class SetupActivity extends AppCompatActivity implements SetupActivityCal
}
});
setupActionBar();
+
+ }
+
+ private void addIndicatorView(ArrayList<View> indicatorViews) {
+ // FIXME: we have to work around a bug in our usage of CardView, that
+ // doesn't let us programmatically add new indicator views as needed.
+ // for some reason the cardBackgroundColor property is ignored if we add
+ // the card view dynamically
+ View v = binding.indicatorContainer.getChildAt(indicatorViews.size());
+ if (v == null) {
+ throw new IllegalStateException("Too few indicator views in layout hard-coded");
+ }
+ v.setVisibility(VISIBLE);
+ indicatorViews.add(v);
}
private void setupActionBar() {
@@ -115,11 +153,6 @@ public class SetupActivity extends AppCompatActivity implements SetupActivityCal
}
}
- public int getCurrentFragmentPosition() {
- return binding.viewPager.getCurrentItem();
- }
-
-
@Override
public void onSetupStepValidationChanged(boolean isValid) {
binding.setupNextButton.setEnabled(isValid);
@@ -170,4 +203,17 @@ public class SetupActivity extends AppCompatActivity implements SetupActivityCal
return provider;
}
+ @Override
+ public int getCurrentPosition() {
+ return binding.viewPager.getCurrentItem();
+ }
+
+ @Override
+ public void onSetupFinished() {
+ Intent intent = getIntent();
+ intent.putExtra(Provider.KEY, provider);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+
} \ No newline at end of file
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivityCallback.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivityCallback.java
index 8fe4118d..3906d73a 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivityCallback.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivityCallback.java
@@ -14,7 +14,6 @@ public interface SetupActivityCallback {
void removeCancelCallback(CancelCallback cancelCallback);
-
void setNavigationButtonHidden(boolean isHidden);
void setCancelButtonHidden(boolean isHidden);
@@ -25,5 +24,8 @@ public interface SetupActivityCallback {
Provider getSelectedProvider();
+ int getCurrentPosition();
+
+ void onSetupFinished();
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java
new file mode 100644
index 00000000..8012fe76
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java
@@ -0,0 +1,63 @@
+package se.leap.bitmaskclient.providersetup.fragments;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.viewpager2.widget.ViewPager2;
+
+import se.leap.bitmaskclient.providersetup.activities.SetupActivityCallback;
+
+public class BaseSetupFragment extends Fragment {
+
+ SetupActivityCallback setupActivityCallback;
+ private boolean callFragmentSelected = false;
+ private final ViewPager2.OnPageChangeCallback viewPagerCallback = new ViewPager2.OnPageChangeCallback() {
+ @Override
+ public void onPageSelected(int position) {
+ super.onPageSelected(position);
+ if (position == BaseSetupFragment.this.position) {
+ handleCallFragmentSelected();
+ } else {
+ callFragmentSelected = false;
+ }
+ }
+ };
+ private final int position;
+
+ public BaseSetupFragment(int position) {
+ this.position = position;
+ }
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ if (getActivity() instanceof SetupActivityCallback) {
+ setupActivityCallback = (SetupActivityCallback) getActivity();
+ setupActivityCallback.registerOnPageChangeCallback(viewPagerCallback);
+ if (setupActivityCallback.getCurrentPosition() == position) {
+ handleCallFragmentSelected();
+ }
+ } else {
+ throw new IllegalStateException("These setup fragments are closely coupled to SetupActivityCallback interface. Activities instantiating them are required to implement the interface");
+ }
+ }
+
+ private void handleCallFragmentSelected() {
+ if (!callFragmentSelected) {
+ callFragmentSelected = true;
+ onFragmentSelected();
+ }
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ setupActivityCallback.removeOnPageChangeCallback(viewPagerCallback);
+ setupActivityCallback = null;
+ }
+
+ public void onFragmentSelected() {
+
+ }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java
index 606de943..1bc12f57 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java
@@ -8,15 +8,18 @@ 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 class CircumventionSetupFragment extends BaseSetupFragment {
- public static CircumventionSetupFragment newInstance() {
- return new CircumventionSetupFragment();
+ private CircumventionSetupFragment(int position) {
+ super(position);
+ }
+
+ public static CircumventionSetupFragment newInstance(int position) {
+ return new CircumventionSetupFragment(position);
}
@Override
@@ -43,4 +46,11 @@ public class CircumventionSetupFragment extends Fragment {
binding.circumventionRadioGroup.check(binding.rbPlainVpn.getId());
return binding.getRoot();
}
+
+ @Override
+ public void onFragmentSelected() {
+ super.onFragmentSelected();
+ setupActivityCallback.setCancelButtonHidden(false);
+ setupActivityCallback.setNavigationButtonHidden(false);
+ }
} \ No newline at end of file
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java
index 42d516a0..41e7cb8e 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java
@@ -8,6 +8,8 @@ import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE;
import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
import static se.leap.bitmaskclient.base.utils.ViewHelper.animateContainerVisibility;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_OK;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.SET_UP_PROVIDER;
import static se.leap.bitmaskclient.tor.TorStatusObservable.getBootstrapProgress;
@@ -25,10 +27,8 @@ 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;
@@ -42,10 +42,9 @@ import se.leap.bitmaskclient.eip.EipSetupObserver;
import se.leap.bitmaskclient.providersetup.ProviderAPICommand;
import se.leap.bitmaskclient.providersetup.TorLogAdapter;
import se.leap.bitmaskclient.providersetup.activities.CancelCallback;
-import se.leap.bitmaskclient.providersetup.activities.SetupActivityCallback;
import se.leap.bitmaskclient.tor.TorStatusObservable;
-public class ConfigureProviderFragment extends Fragment implements Observer, CancelCallback, EipSetupListener {
+public class ConfigureProviderFragment extends BaseSetupFragment implements Observer, CancelCallback, EipSetupListener {
private static final String TAG = ConfigureProviderFragment.class.getSimpleName();
@@ -54,15 +53,13 @@ public class ConfigureProviderFragment extends Fragment implements Observer, Can
}
FConfigureProviderBinding binding;
- private SetupActivityCallback setupActivityCallback;
private boolean isExpanded = false;
- private final int position;
- private ViewPager2.OnPageChangeCallback viewPagerCallback;
+ private boolean ignoreProviderAPIUpdates = false;
private TorLogAdapter torLogAdapter;
- public ConfigureProviderFragment(int position) {
- this.position = position;
+ private ConfigureProviderFragment(int position) {
+ super(position);
}
@Override
@@ -99,39 +96,29 @@ public class ConfigureProviderFragment extends Fragment implements Observer, Can
}
@Override
+ public void onFragmentSelected() {
+ super.onFragmentSelected();
+ ignoreProviderAPIUpdates = false;
+ binding.detailContainer.setVisibility(PreferenceHelper.getUseSnowflake(getContext()) ? VISIBLE : GONE);
+ setupActivityCallback.setNavigationButtonHidden(true);
+ setupActivityCallback.setCancelButtonHidden(false);
+ ProviderAPICommand.execute(getContext(), SET_UP_PROVIDER, setupActivityCallback.getSelectedProvider());
+ }
+
+ @Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
- if (getActivity() instanceof SetupActivityCallback) {
- setupActivityCallback = (SetupActivityCallback) 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);
- setupActivityCallback.setNavigationButtonHidden(true);
- setupActivityCallback.setCancelButtonHidden(false);
- ProviderAPICommand.execute(context, SET_UP_PROVIDER, setupActivityCallback.getSelectedProvider());
- }
- }
- };
- setupActivityCallback.registerOnPageChangeCallback(viewPagerCallback);
- setupActivityCallback.registerCancelCallback(this);
- }
+ setupActivityCallback.registerCancelCallback(this);
TorStatusObservable.getInstance().addObserver(this);
EipSetupObserver.addListener(this);
}
@Override
public void onDetach() {
- super.onDetach();
+ setupActivityCallback.removeCancelCallback(this);
TorStatusObservable.getInstance().deleteObserver(this);
- if (setupActivityCallback != null) {
- setupActivityCallback.removeOnPageChangeCallback(viewPagerCallback);
- setupActivityCallback.removeCancelCallback(this);
- setupActivityCallback = null;
- }
EipSetupObserver.removeListener(this);
+ super.onDetach();
}
protected void showConnectionDetails() {
@@ -185,7 +172,7 @@ public class ConfigureProviderFragment extends Fragment implements Observer, Can
@Override
public void onCanceled() {
-
+ ignoreProviderAPIUpdates = true;
}
@Override
@@ -198,10 +185,23 @@ public class ConfigureProviderFragment extends Fragment implements Observer, Can
if (resultData == null) {
resultData = Bundle.EMPTY;
}
+ Provider provider = resultData.getParcelable(PROVIDER_KEY);
+ if (ignoreProviderAPIUpdates ||
+ provider == null ||
+ !setupActivityCallback.getSelectedProvider().getDomain().equals(provider.getDomain())) {
+ return;
+ }
if (resultCode == PROVIDER_OK) {
- Provider provider = resultData.getParcelable(PROVIDER_KEY);
+ setupActivityCallback.onProviderSelected(provider);
+ if (provider.allowsAnonymous()) {
+ ProviderAPICommand.execute(this.getContext(), DOWNLOAD_VPN_CERTIFICATE, provider);
+ } else {
+ // TODO: implement error message that this client only supports anonymous usage
+ }
+ } else if (resultCode == CORRECTLY_DOWNLOADED_VPN_CERTIFICATE) {
setupActivityCallback.onProviderSelected(provider);
setupActivityCallback.onConfigurationSuccess();
}
}
+
} \ No newline at end of file
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java
new file mode 100644
index 00000000..18751ee5
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java
@@ -0,0 +1,92 @@
+package se.leap.bitmaskclient.providersetup.fragments;
+
+import android.Manifest;
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toast;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import se.leap.bitmaskclient.databinding.FVpnPermissionSetupBinding;
+
+public class EmptyPermissionSetupFragment extends BaseSetupFragment {
+
+ private String notificationPermissionAction = null;
+ private Intent vpnPermissionIntent = null;
+
+ private final ActivityResultLauncher<String> requestNotificationPermissionLauncher =
+ registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> {
+ if (isGranted) {
+ if (setupActivityCallback != null) {
+ setupActivityCallback.onConfigurationSuccess();
+ }
+ } else {
+ Toast.makeText(getContext(), "Permission request failed :(", Toast.LENGTH_LONG).show();
+ setupActivityCallback.setNavigationButtonHidden(false);
+ // TODO: implement sth. useful
+ }
+ });
+
+ private final ActivityResultLauncher<Intent> requestVpnPermissionLauncher =
+ registerForActivityResult(
+ new ActivityResultContracts.StartActivityForResult(),
+ result -> {
+ if (result.getResultCode() == Activity.RESULT_OK) {
+ if (setupActivityCallback != null) {
+ setupActivityCallback.onConfigurationSuccess();
+ }
+ } else {
+ Toast.makeText(getContext(), "Permission request failed :(", Toast.LENGTH_LONG).show();
+ setupActivityCallback.setNavigationButtonHidden(false);
+ // TODO: implement sth. useful
+ }
+ }
+ );
+
+ private EmptyPermissionSetupFragment(int position, String permissionAction) {
+ super(position);
+ this.notificationPermissionAction = permissionAction;
+ }
+
+ private EmptyPermissionSetupFragment(int position, Intent vpnPermissionIntent) {
+ super(position);
+ this.vpnPermissionIntent = vpnPermissionIntent;
+ }
+
+ public static EmptyPermissionSetupFragment newInstance(int position, Intent vpnPermissionIntent) {
+ return new EmptyPermissionSetupFragment(position, vpnPermissionIntent);
+ }
+
+ public static EmptyPermissionSetupFragment newInstance(int position, String notificationPermissionAction) {
+ return new EmptyPermissionSetupFragment(position, notificationPermissionAction);
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ FVpnPermissionSetupBinding binding = FVpnPermissionSetupBinding.inflate(inflater, container, false);
+ return binding.getRoot();
+ }
+
+ @Override
+ public void onFragmentSelected() {
+ super.onFragmentSelected();
+ if (notificationPermissionAction != null) {
+ requestNotificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);
+ } else if (vpnPermissionIntent != null) {
+ requestVpnPermissionLauncher.launch(vpnPermissionIntent);
+ }
+
+ setupActivityCallback.setNavigationButtonHidden(true);
+ setupActivityCallback.setCancelButtonHidden(true);
+ }
+
+
+} \ No newline at end of file
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/NotificationSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/NotificationSetupFragment.java
new file mode 100644
index 00000000..72374bb9
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/NotificationSetupFragment.java
@@ -0,0 +1,37 @@
+package se.leap.bitmaskclient.providersetup.fragments;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import se.leap.bitmaskclient.databinding.FNotificationSetupBinding;
+
+public class NotificationSetupFragment extends BaseSetupFragment {
+
+ private NotificationSetupFragment(int position) {
+ super(position);
+ }
+
+ public static NotificationSetupFragment newInstance(int position) {
+ return new NotificationSetupFragment(position);
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ FNotificationSetupBinding binding = FNotificationSetupBinding.inflate(inflater, container, false);
+ return binding.getRoot();
+ }
+
+ @Override
+ public void onFragmentSelected() {
+ super.onFragmentSelected();
+ setupActivityCallback.setNavigationButtonHidden(false);
+ setupActivityCallback.setCancelButtonHidden(true);
+ }
+
+} \ No newline at end of file
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java
index 6ebb149c..7f80a99d 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java
@@ -14,9 +14,7 @@ import android.widget.RadioButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
-import androidx.viewpager2.widget.ViewPager2;
import java.util.ArrayList;
@@ -25,33 +23,22 @@ import se.leap.bitmaskclient.base.models.Provider;
import se.leap.bitmaskclient.base.models.ProviderObservable;
import se.leap.bitmaskclient.databinding.FProviderSelectionBinding;
import se.leap.bitmaskclient.providersetup.activities.CancelCallback;
-import se.leap.bitmaskclient.providersetup.activities.SetupActivityCallback;
import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModel;
import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModelFactory;
-public class ProviderSelectionFragment extends Fragment implements CancelCallback {
+public class ProviderSelectionFragment extends BaseSetupFragment implements CancelCallback {
private ProviderSelectionViewModel viewModel;
private ArrayList<RadioButton> radioButtons;
- private SetupActivityCallback setupCallback;
private FProviderSelectionBinding binding;
- private final ViewPager2.OnPageChangeCallback onPageChangeCallback = new ViewPager2.OnPageChangeCallback() {
- @Override
- public void onPageSelected(int position) {
- super.onPageSelected(position);
- if (position == 0) {
- if (setupCallback != null) {
- setupCallback.setCancelButtonHidden(!ProviderObservable.getInstance().getCurrentProvider().isConfigured());
- setupCallback.setNavigationButtonHidden(false);
- }
- }
- }
- };
+ private ProviderSelectionFragment(int position) {
+ super(position);
+ }
- public static ProviderSelectionFragment newInstance() {
- return new ProviderSelectionFragment();
+ public static ProviderSelectionFragment newInstance(int position) {
+ return new ProviderSelectionFragment(position);
}
@Override
@@ -92,13 +79,11 @@ public class ProviderSelectionFragment extends Fragment implements CancelCallbac
}
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()));
- }
+ setupActivityCallback.onSetupStepValidationChanged(viewModel.isValidConfig());
+ if (checkedId != ADD_PROVIDER) {
+ setupActivityCallback.onProviderSelected(viewModel.getProvider(checkedId));
+ } else if (viewModel.isValidConfig()) {
+ setupActivityCallback.onProviderSelected(new Provider(binding.editCustomProvider.getText().toString()));
}
});
binding.providerRadioGroup.check(viewModel.getSelected());
@@ -110,10 +95,9 @@ public class ProviderSelectionFragment extends Fragment implements CancelCallbac
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
viewModel.setCustomUrl(s.toString());
- if (setupCallback == null) return;
- setupCallback.onSetupStepValidationChanged(viewModel.isValidConfig());
+ setupActivityCallback.onSetupStepValidationChanged(viewModel.isValidConfig());
if (viewModel.isValidConfig()) {
- setupCallback.onProviderSelected(new Provider(s.toString()));
+ setupActivityCallback.onProviderSelected(new Provider(s.toString()));
}
}
@@ -124,23 +108,22 @@ public class ProviderSelectionFragment extends Fragment implements CancelCallbac
}
@Override
+ public void onFragmentSelected() {
+ super.onFragmentSelected();
+ setupActivityCallback.setCancelButtonHidden(!ProviderObservable.getInstance().getCurrentProvider().isConfigured());
+ setupActivityCallback.setNavigationButtonHidden(false);
+ }
+
+ @Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
- if (getActivity() instanceof SetupActivityCallback) {
- setupCallback = (SetupActivityCallback) getActivity();
- setupCallback.registerOnPageChangeCallback(onPageChangeCallback);
- setupCallback.registerCancelCallback(this);
- }
+ setupActivityCallback.registerCancelCallback(this);
}
@Override
public void onDetach() {
+ setupActivityCallback.removeCancelCallback(this);
super.onDetach();
- if (setupCallback != null) {
- setupCallback.removeOnPageChangeCallback(onPageChangeCallback);
- setupCallback.removeCancelCallback(this);
- }
- setupCallback = null;
}
@Override
@@ -158,7 +141,7 @@ public class ProviderSelectionFragment extends Fragment implements CancelCallbac
@Override
public void onResume() {
super.onResume();
- setupCallback.onSetupStepValidationChanged(viewModel.isValidConfig());
+ setupActivityCallback.onSetupStepValidationChanged(viewModel.isValidConfig());
}
@Override
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupFragmentFactory.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupFragmentFactory.java
new file mode 100644
index 00000000..fd76a841
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupFragmentFactory.java
@@ -0,0 +1,61 @@
+package se.leap.bitmaskclient.providersetup.fragments;
+
+import android.Manifest;
+import android.content.Intent;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+
+import java.util.ArrayList;
+
+public class SetupFragmentFactory {
+ public static final int PROVIDER_SELECTION_FRAGMENT = 0;
+ public static final int CIRCUMVENTION_SETUP_FRAGMENT = 1;
+ public static final int CONFIGURE_PROVIDER_FRAGMENT = 2;
+ public static final int VPN_PERMISSON_EDUCATIONAL_FRAGMENT = 3;
+ public static final int VPN_PERMISSON_FRAGMENT = 4;
+ public static final int NOTIFICATION_PERMISSON_EDUCATIONAL_FRAGMENT = 5;
+ public static final int NOTIFICATION_PERMISSON_FRAGMENT = 6;
+
+ public static final int SUCCESS_FRAGMENT = 7;
+
+ private final Intent vpnPermissionRequest;
+
+ private final ArrayList<Integer> fragmentTypes;
+
+ public SetupFragmentFactory(@NonNull ArrayList<Integer> fragmentTypes, Intent vpnPermissionRequest) {
+ this.fragmentTypes = fragmentTypes;
+ this.vpnPermissionRequest = vpnPermissionRequest;
+ }
+
+ public Fragment createFragment(int position) {
+ if (position < 0 || position >= fragmentTypes.size()) {
+ throw new IllegalStateException("Illegal fragment position");
+ }
+ int type = fragmentTypes.get(position);
+ switch (type) {
+ case PROVIDER_SELECTION_FRAGMENT:
+ return ProviderSelectionFragment.newInstance(position);
+ case CIRCUMVENTION_SETUP_FRAGMENT:
+ return CircumventionSetupFragment.newInstance(position);
+ case CONFIGURE_PROVIDER_FRAGMENT:
+ return ConfigureProviderFragment.newInstance(position);
+ case NOTIFICATION_PERMISSON_EDUCATIONAL_FRAGMENT:
+ return NotificationSetupFragment.newInstance(position);
+ case NOTIFICATION_PERMISSON_FRAGMENT:
+ return EmptyPermissionSetupFragment.newInstance(position, Manifest.permission.POST_NOTIFICATIONS);
+ case VPN_PERMISSON_EDUCATIONAL_FRAGMENT:
+ return VpnPermissionSetupFragment.newInstance(position);
+ case VPN_PERMISSON_FRAGMENT:
+ return EmptyPermissionSetupFragment.newInstance(position, vpnPermissionRequest);
+ case SUCCESS_FRAGMENT:
+ return SetupSuccessFragment.newInstance(position);
+ default:
+ throw new IllegalArgumentException("Unexpected fragment type: " + type);
+ }
+ }
+
+ public int getItemCount() {
+ return fragmentTypes.size();
+ }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java
new file mode 100644
index 00000000..3e241012
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java
@@ -0,0 +1,40 @@
+package se.leap.bitmaskclient.providersetup.fragments;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import se.leap.bitmaskclient.databinding.FSetupSuccessBinding;
+
+public class SetupSuccessFragment extends BaseSetupFragment {
+
+
+ private SetupSuccessFragment(int position) {
+ super(position);
+ }
+
+ public static SetupSuccessFragment newInstance(int position) {
+ return new SetupSuccessFragment(position);
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ FSetupSuccessBinding binding = FSetupSuccessBinding.inflate(inflater, container, false);
+
+ binding.mainButton.setOnClickListener(v -> setupActivityCallback.onSetupFinished());
+ return binding.getRoot();
+ }
+
+ @Override
+ public void onFragmentSelected() {
+ super.onFragmentSelected();
+ setupActivityCallback.setNavigationButtonHidden(true);
+ setupActivityCallback.setCancelButtonHidden(true);
+ }
+
+} \ No newline at end of file
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/VpnPermissionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/VpnPermissionSetupFragment.java
new file mode 100644
index 00000000..a6af0b36
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/VpnPermissionSetupFragment.java
@@ -0,0 +1,44 @@
+package se.leap.bitmaskclient.providersetup.fragments;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import se.leap.bitmaskclient.databinding.FVpnPermissionSetupBinding;
+
+public class VpnPermissionSetupFragment extends BaseSetupFragment {
+
+
+ private VpnPermissionSetupFragment(int position) {
+ super(position);
+ }
+
+ public static VpnPermissionSetupFragment newInstance(int position) {
+ return new VpnPermissionSetupFragment(position);
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ FVpnPermissionSetupBinding binding = FVpnPermissionSetupBinding.inflate(inflater, container, false);
+ return binding.getRoot();
+ }
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ }
+
+ @Override
+ public void onFragmentSelected() {
+ super.onFragmentSelected();
+ setupActivityCallback.setNavigationButtonHidden(false);
+ setupActivityCallback.setCancelButtonHidden(true);
+ }
+
+} \ No newline at end of file