From 0fa7ae499185fefa732a7bc02a8e22ea5da92ec7 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Wed, 2 Aug 2023 12:34:45 +0200 Subject: * 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!") --- .../providersetup/SetupViewPagerAdapter.java | 56 +++++++------ .../providersetup/activities/SetupActivity.java | 82 ++++++++++++++----- .../activities/SetupActivityCallback.java | 4 +- .../providersetup/fragments/BaseSetupFragment.java | 63 +++++++++++++++ .../fragments/CircumventionSetupFragment.java | 18 ++++- .../fragments/ConfigureProviderFragment.java | 68 ++++++++-------- .../fragments/EmptyPermissionSetupFragment.java | 92 ++++++++++++++++++++++ .../fragments/NotificationSetupFragment.java | 37 +++++++++ .../fragments/ProviderSelectionFragment.java | 63 ++++++--------- .../fragments/SetupFragmentFactory.java | 61 ++++++++++++++ .../fragments/SetupSuccessFragment.java | 40 ++++++++++ .../fragments/VpnPermissionSetupFragment.java | 44 +++++++++++ 12 files changed, 506 insertions(+), 122 deletions(-) create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/BaseSetupFragment.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/EmptyPermissionSetupFragment.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/NotificationSetupFragment.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupFragmentFactory.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/SetupSuccessFragment.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/VpnPermissionSetupFragment.java (limited to 'app/src/main/java/se/leap') 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 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 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 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 @@ -98,40 +95,30 @@ public class ConfigureProviderFragment extends Fragment implements Observer, Can binding = null; } + @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 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 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 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())); } } @@ -123,24 +107,23 @@ public class ProviderSelectionFragment extends Fragment implements CancelCallbac return binding.getRoot(); } + @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 fragmentTypes; + + public SetupFragmentFactory(@NonNull ArrayList 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 -- cgit v1.2.3