summaryrefslogtreecommitdiff
path: root/app/src/main/java/se/leap/bitmaskclient/providersetup/activities
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/activities')
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/AbstractProviderDetailActivity.java109
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/AddProviderBaseActivity.java125
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ButterKnifeActivity.java46
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ConfigWizardBaseActivity.java289
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/CustomProviderSetupActivity.java121
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/LoginActivity.java32
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderCredentialsBaseActivity.java479
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderListBaseActivity.java193
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderSetupBaseActivity.java240
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SignupActivity.java55
10 files changed, 1689 insertions, 0 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/AbstractProviderDetailActivity.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/AbstractProviderDetailActivity.java
new file mode 100644
index 00000000..b7325e03
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/AbstractProviderDetailActivity.java
@@ -0,0 +1,109 @@
+package se.leap.bitmaskclient.providersetup.activities;
+
+import android.content.Intent;
+import android.os.Bundle;
+import androidx.annotation.Nullable;
+import androidx.appcompat.widget.AppCompatTextView;
+import android.util.Log;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+import butterknife.InjectView;
+import se.leap.bitmaskclient.base.models.Provider;
+import se.leap.bitmaskclient.R;
+
+import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
+import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_CONFIGURE_LEAP;
+
+public abstract class AbstractProviderDetailActivity extends ConfigWizardBaseActivity {
+
+ final public static String TAG = "providerDetailActivity";
+
+ @InjectView(R.id.provider_detail_description)
+ AppCompatTextView description;
+
+ @InjectView(R.id.provider_detail_options)
+ ListView options;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ provider = getIntent().getParcelableExtra(PROVIDER_KEY);
+ setContentView(R.layout.a_provider_detail);
+
+ if (provider == null) {
+ return;
+ }
+
+
+ setProviderHeaderText(provider.getName());
+ description.setText(provider.getDescription());
+
+ // Show only the options allowed by the provider
+ ArrayList<String> optionsList = new ArrayList<>();
+ if (provider.allowsRegistered()) {
+ optionsList.add(getString(R.string.login_to_profile));
+ optionsList.add(getString(R.string.create_profile));
+ if (provider.allowsAnonymous()) {
+ optionsList.add(getString(R.string.use_anonymously_button));
+ }
+ } else {
+ onAnonymouslySelected();
+ }
+
+
+ options.setAdapter(new ArrayAdapter<>(
+ this,
+ R.layout.v_single_list_item,
+ android.R.id.text1,
+ optionsList.toArray(new String[optionsList.size()])
+ ));
+ options.setOnItemClickListener((parent, view, position, id) -> {
+ String text = ((TextView) view).getText().toString();
+ Intent intent;
+ if (text.equals(getString(R.string.login_to_profile))) {
+ Log.d(TAG, "login selected");
+ intent = new Intent(getApplicationContext(), LoginActivity.class);
+ } else if (text.equals(getString(R.string.create_profile))) {
+ Log.d(TAG, "signup selected");
+ intent = new Intent(getApplicationContext(), SignupActivity.class);
+ } else {
+ onAnonymouslySelected();
+ return;
+ }
+ intent.putExtra(PROVIDER_KEY, provider);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ startActivityForResult(intent, REQUEST_CODE_CONFIGURE_LEAP);
+ });
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ provider = intent.getParcelableExtra(PROVIDER_KEY);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_CODE_CONFIGURE_LEAP) {
+ if (resultCode == RESULT_OK) {
+ setResult(resultCode, data);
+ finish();
+ }
+ }
+ }
+
+ private void onAnonymouslySelected() {
+ Intent intent;
+ Log.d(TAG, "use anonymously selected");
+ intent = new Intent();
+ intent.putExtra(Provider.KEY, provider);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/AddProviderBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/AddProviderBaseActivity.java
new file mode 100644
index 00000000..0031f48d
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/AddProviderBaseActivity.java
@@ -0,0 +1,125 @@
+package se.leap.bitmaskclient.providersetup.activities;
+
+import android.content.Intent;
+import android.os.Bundle;
+import com.google.android.material.textfield.TextInputEditText;
+import com.google.android.material.textfield.TextInputLayout;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.View;
+import android.widget.Button;
+
+import butterknife.InjectView;
+import se.leap.bitmaskclient.R;
+
+import static se.leap.bitmaskclient.providersetup.activities.ProviderListBaseActivity.EXTRAS_KEY_INVALID_URL;
+
+/**
+ * Created by cyberta on 30.06.18.
+ */
+
+public abstract class AddProviderBaseActivity extends ConfigWizardBaseActivity {
+
+ final public static String EXTRAS_KEY_NEW_URL = "NEW_URL";
+
+ @InjectView(R.id.text_uri_error)
+ TextInputLayout urlError;
+
+ @InjectView(R.id.text_uri)
+ TextInputEditText editUrl;
+
+ @InjectView(R.id.button_cancel)
+ Button cancelButton;
+
+ @InjectView(R.id.button_save)
+ Button saveButton;
+
+
+ protected void init() {
+ Bundle extras = this.getIntent().getExtras();
+ if (extras != null && extras.containsKey(EXTRAS_KEY_INVALID_URL)) {
+ editUrl.setText(extras.getString(EXTRAS_KEY_INVALID_URL));
+ saveButton.setEnabled(true);
+ }
+
+ setupSaveButton();
+ setupCancelButton();
+ setUpListeners();
+ setUpInitialUI();
+ }
+
+ public abstract void setupSaveButton();
+
+ private void setupCancelButton() {
+ cancelButton.setOnClickListener(new View.OnClickListener() {
+ public void onClick(View v) {
+ finish();
+ }
+ });
+ }
+
+ private void setUpInitialUI() {
+ setProviderHeaderText(R.string.add_provider);
+ hideProgressBar();
+ }
+
+ protected void saveProvider() {
+ String entered_url = getURL();
+ if (validURL(entered_url)) {
+ Intent intent = this.getIntent();
+ intent.putExtra(EXTRAS_KEY_NEW_URL, entered_url);
+ setResult(RESULT_OK, intent);
+ finish();
+ } else {
+ editUrl.setText("");
+ urlError.setError(getString(R.string.not_valid_url_entered));
+ }
+ }
+
+ private void setUpListeners() {
+
+ editUrl.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) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (!validURL(getURL())) {
+ urlError.setError(getString(R.string.not_valid_url_entered));
+ saveButton.setEnabled(false);
+
+ } else {
+ urlError.setError(null);
+ saveButton.setEnabled(true);
+ }
+ }
+ });
+ }
+
+ private String getURL() {
+ String entered_url = editUrl.getText().toString().trim();
+ if (entered_url.contains("www.")) entered_url = entered_url.replaceFirst("www.", "");
+ if (!entered_url.startsWith("https://")) {
+ if (entered_url.startsWith("http://")) {
+ entered_url = entered_url.substring("http://".length());
+ }
+ entered_url = "https://".concat(entered_url);
+ }
+ return entered_url;
+ }
+
+ /**
+ * Checks if the entered url is valid or not.
+ *
+ * @param enteredUrl
+ * @return true if it's not empty nor contains only the protocol.
+ */
+ boolean validURL(String enteredUrl) {
+ return android.util.Patterns.WEB_URL.matcher(enteredUrl).matches();
+ }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ButterKnifeActivity.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ButterKnifeActivity.java
new file mode 100644
index 00000000..22edd9ee
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ButterKnifeActivity.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2020 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient.providersetup.activities;
+
+import androidx.appcompat.app.AppCompatActivity;
+import android.view.View;
+
+import butterknife.ButterKnife;
+
+/**
+ * Automatically inject with ButterKnife after calling setContentView
+ */
+
+public abstract class ButterKnifeActivity extends AppCompatActivity {
+
+ @Override
+ public void setContentView(View view) {
+ super.setContentView(view);
+ ButterKnife.inject(this);
+ }
+
+ @Override
+ public void setContentView(int layoutResID) {
+ super.setContentView(layoutResID);
+ ButterKnife.inject(this);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ }
+}
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
new file mode 100644
index 00000000..3712c544
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ConfigWizardBaseActivity.java
@@ -0,0 +1,289 @@
+package se.leap.bitmaskclient.providersetup.activities;
+
+import android.content.SharedPreferences;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import androidx.annotation.DrawableRes;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.constraintlayout.widget.Guideline;
+import androidx.core.content.ContextCompat;
+import androidx.appcompat.widget.AppCompatTextView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+
+import butterknife.InjectView;
+import butterknife.Optional;
+import se.leap.bitmaskclient.base.models.Provider;
+import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.base.views.ProviderHeaderView;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
+import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES;
+
+/**
+ * Base Activity for configuration wizard activities
+ *
+ * Created by fupduck on 09.01.18.
+ */
+
+public abstract class ConfigWizardBaseActivity extends ButterKnifeActivity {
+
+ private static final String TAG = ConfigWizardBaseActivity.class.getName();
+ public static final float GUIDE_LINE_COMPACT_DELTA = 0.1f;
+ protected SharedPreferences preferences;
+
+ @InjectView(R.id.header)
+ ProviderHeaderView providerHeaderView;
+
+ //Add provider screen has no loading screen
+ @Optional
+ @InjectView(R.id.loading_screen)
+ protected LinearLayout loadingScreen;
+
+ @Optional
+ @InjectView(R.id.progressbar)
+ protected ProgressBar progressBar;
+
+ @Optional
+ @InjectView(R.id.progressbar_description)
+ protected AppCompatTextView progressbarText;
+
+ //Only tablet layouts have guidelines as they are based on a ConstraintLayout
+ @Optional
+ @InjectView(R.id.guideline_top)
+ protected Guideline guideline_top;
+
+ @Optional
+ @InjectView(R.id.guideline_bottom)
+ protected Guideline guideline_bottom;
+
+ @InjectView(R.id.content)
+ protected LinearLayout content;
+
+ protected Provider provider;
+
+ protected boolean isCompactLayout = false;
+ protected boolean isActivityShowing;
+
+ private float defaultGuidelineTopPercentage;
+ private float defaultGuidelineBottomPercentage;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE);
+ provider = getIntent().getParcelableExtra(PROVIDER_KEY);
+ }
+
+ @Override
+ public void setContentView(View view) {
+ super.setContentView(view);
+ initContentView();
+ }
+
+ @Override
+ public void setContentView(int layoutResID) {
+ super.setContentView(layoutResID);
+ initContentView();
+ }
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ super.setContentView(view, params);
+ initContentView();
+ }
+
+ private void initContentView() {
+ if (provider != null) {
+ setProviderHeaderText(provider.getName());
+ }
+ setProgressbarColorForPreLollipop();
+ setDefaultGuidelineValues();
+ setGlobalLayoutChangeListener();
+ }
+
+ private void setDefaultGuidelineValues() {
+ if (isTabletLayout()) {
+ defaultGuidelineTopPercentage = ((ConstraintLayout.LayoutParams) guideline_top.getLayoutParams()).guidePercent;
+ defaultGuidelineBottomPercentage = ((ConstraintLayout.LayoutParams) guideline_bottom.getLayoutParams()).guidePercent;
+ }
+ }
+
+ private void setProgressbarColorForPreLollipop() {
+ if (progressBar == null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ return;
+ }
+ progressBar.getIndeterminateDrawable().setColorFilter(
+ ContextCompat.getColor(this, R.color.colorPrimary),
+ PorterDuff.Mode.SRC_IN);
+ }
+
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (provider != null) {
+ outState.putParcelable(PROVIDER_KEY, provider);
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ isActivityShowing = false;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ isActivityShowing = true;
+ }
+
+ protected void restoreState(Bundle savedInstanceState) {
+ if (savedInstanceState != null && savedInstanceState.containsKey(PROVIDER_KEY)) {
+ provider = savedInstanceState.getParcelable(PROVIDER_KEY);
+ }
+ }
+
+ protected void setProviderHeaderLogo(@DrawableRes int providerHeaderLogo) {
+ providerHeaderView.setLogo(providerHeaderLogo);
+ }
+
+ protected void setProviderHeaderText(String providerHeaderText) {
+ providerHeaderView.setTitle(providerHeaderText);
+ }
+
+ protected void setProviderHeaderText(@StringRes int providerHeaderText) {
+ providerHeaderView.setTitle(providerHeaderText);
+ }
+
+ protected void hideProgressBar() {
+ if (loadingScreen == null) {
+ return;
+ }
+ loadingScreen.setVisibility(GONE);
+ content.setVisibility(VISIBLE);
+ }
+
+ protected void showProgressBar() {
+ if (loadingScreen == null) {
+ return;
+ }
+ content.setVisibility(GONE);
+ loadingScreen.setVisibility(VISIBLE);
+ }
+
+ protected void setProgressbarText(@StringRes int progressbarText) {
+ if (this.progressbarText == null) {
+ return;
+ }
+ this.progressbarText.setText(progressbarText);
+ }
+
+
+ protected void showCompactLayout() {
+ if (isCompactLayout) {
+ return;
+ }
+
+ if (isTabletLayoutInLandscape() || isPhoneLayout()) {
+ providerHeaderView.showCompactLayout();
+ }
+
+ showIncreasedTabletContentArea();
+ isCompactLayout = true;
+ }
+
+ protected void showStandardLayout() {
+ if (!isCompactLayout) {
+ return;
+ }
+ providerHeaderView.showStandardLayout();
+ showStandardTabletContentArea();
+ isCompactLayout = false;
+ }
+
+ private boolean isTabletLayoutInLandscape() {
+ // TabletLayout is based on a ConstraintLayout and uses Guidelines whereas the phone layout
+ // has no such elements in it's layout xml file
+ return guideline_top != null &&
+ guideline_bottom != null &&
+ getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
+ }
+
+ protected boolean isPhoneLayout() {
+ return guideline_top == null && guideline_bottom == null;
+ }
+
+ protected boolean isTabletLayout() {
+ return guideline_top != null && guideline_bottom != null;
+ }
+
+ /**
+ * Increases the white content area in tablet layouts
+ */
+ private void showIncreasedTabletContentArea() {
+ if (isPhoneLayout()) {
+ return;
+ }
+ ConstraintLayout.LayoutParams guideLineTopParams = (ConstraintLayout.LayoutParams) guideline_top.getLayoutParams();
+ float increasedTopPercentage = defaultGuidelineTopPercentage - GUIDE_LINE_COMPACT_DELTA;
+ guideLineTopParams.guidePercent = increasedTopPercentage > 0f ? increasedTopPercentage : 0f;
+ guideline_top.setLayoutParams(guideLineTopParams);
+
+ ConstraintLayout.LayoutParams guideLineBottomParams = (ConstraintLayout.LayoutParams) guideline_bottom.getLayoutParams();
+ float increasedBottomPercentage = defaultGuidelineBottomPercentage + GUIDE_LINE_COMPACT_DELTA;
+ guideLineBottomParams.guidePercent = increasedBottomPercentage < 1f ? increasedBottomPercentage : 1f;
+ guideline_bottom.setLayoutParams(guideLineBottomParams);
+ }
+
+ /**
+ * Restores the default size of the white content area in tablet layouts
+ */
+ private void showStandardTabletContentArea() {
+ if (isPhoneLayout()) {
+ return;
+ }
+ ConstraintLayout.LayoutParams guideLineTopParams = (ConstraintLayout.LayoutParams) guideline_top.getLayoutParams();
+ guideLineTopParams.guidePercent = defaultGuidelineTopPercentage;
+ guideline_top.setLayoutParams(guideLineTopParams);
+
+ ConstraintLayout.LayoutParams guideLineBottomParams = (ConstraintLayout.LayoutParams) guideline_bottom.getLayoutParams();
+ guideLineBottomParams.guidePercent = defaultGuidelineBottomPercentage;
+ guideline_bottom.setLayoutParams(guideLineBottomParams);
+ }
+
+ /**
+ * Checks if the keyboard is shown and switches between the standard layout and the compact layout
+ */
+ private void setGlobalLayoutChangeListener() {
+ final View rootView = content.getRootView();
+ rootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ Rect r = new Rect();
+ //r will be populated with the coordinates of your view that area still visible.
+ rootView.getWindowVisibleDisplayFrame(r);
+
+ float deltaHiddenScreen = 1f - ((float) (r.bottom - r.top) / (float) rootView.getHeight());
+ if (deltaHiddenScreen > 0.25f) {
+ // if more than 1/4 of the screen is hidden
+ showCompactLayout();
+ } else {
+ showStandardLayout();
+ }
+ }
+ });
+ }
+
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/CustomProviderSetupActivity.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/CustomProviderSetupActivity.java
new file mode 100644
index 00000000..161c53d3
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/CustomProviderSetupActivity.java
@@ -0,0 +1,121 @@
+/**
+ * Copyright (c) 2018 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient.providersetup.activities;
+
+import android.content.Intent;
+import android.os.Bundle;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import se.leap.bitmaskclient.BuildConfig;
+import se.leap.bitmaskclient.base.models.Provider;
+import se.leap.bitmaskclient.providersetup.ProviderAPICommand;
+import se.leap.bitmaskclient.R;
+
+import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_CONFIGURE_LEAP;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.SET_UP_PROVIDER;
+import static se.leap.bitmaskclient.providersetup.ProviderSetupInterface.ProviderConfigState.SETTING_UP_PROVIDER;
+import static se.leap.bitmaskclient.base.utils.ConfigHelper.preferAnonymousUsage;
+
+/**
+ * Created by cyberta on 17.08.18.
+ */
+
+public class CustomProviderSetupActivity extends ProviderSetupBaseActivity {
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setUpInitialUI();
+ restoreState(savedInstanceState);
+ setProvider(new Provider(BuildConfig.customProviderUrl, BuildConfig.geoipUrl, BuildConfig.customProviderIp, BuildConfig.customProviderApiIp));
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (getConfigState() == ProviderConfigState.PROVIDER_NOT_SET) {
+ showProgressBar();
+ setupProvider();
+ }
+ }
+
+ private void setUpInitialUI() {
+ setContentView(R.layout.a_custom_provider_setup);
+ setProviderHeaderText(R.string.setup_provider);
+ hideProgressBar();
+ }
+
+ private void setupProvider() {
+ setProviderConfigState(SETTING_UP_PROVIDER);
+ ProviderAPICommand.execute(this, SET_UP_PROVIDER, getProvider());
+ }
+
+ // ------- ProviderSetupInterface ---v
+ @Override
+ public void handleProviderSetUp(Provider provider) {
+ setProvider(provider);
+ if (provider.allowsAnonymous()) {
+ downloadVpnCertificate();
+ } else {
+ showProviderDetails();
+ }
+ }
+
+ @Override
+ public void handleCorrectlyDownloadedCertificate(Provider provider) {
+ if (preferAnonymousUsage()) {
+ finishWithSetupWithProvider(provider);
+ } else {
+ this.provider = provider;
+ showProviderDetails();
+ }
+ }
+
+ // ------- DownloadFailedDialogInterface ---v
+ @Override
+ public void retrySetUpProvider(@NonNull Provider provider) {
+ setupProvider();
+ showProgressBar();
+ }
+
+ @Override
+ public void cancelSettingUpProvider() {
+ super.cancelSettingUpProvider();
+ finish();
+ }
+
+ @Override
+ public void addAndSelectNewProvider(String url) {
+ // ignore
+ }
+
+ private void finishWithSetupWithProvider(Provider provider) {
+ Intent intent = new Intent();
+ intent.putExtra(Provider.KEY, provider);
+ setResult(RESULT_OK, intent);
+ finish();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_CODE_CONFIGURE_LEAP) {
+ setResult(resultCode, data);
+ finish();
+ }
+ }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/LoginActivity.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/LoginActivity.java
new file mode 100644
index 00000000..a8bac6d8
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/LoginActivity.java
@@ -0,0 +1,32 @@
+package se.leap.bitmaskclient.providersetup.activities;
+
+import android.os.Bundle;
+import androidx.annotation.Nullable;
+
+import butterknife.OnClick;
+import se.leap.bitmaskclient.R;
+
+/**
+ * Activity to login to chosen Provider
+ *
+ * Created by fupduck on 09.01.18.
+ */
+
+public class LoginActivity extends ProviderCredentialsBaseActivity {
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setProgressbarText(R.string.logging_in);
+ setProviderHeaderLogo(R.drawable.logo);
+ setProviderHeaderText(R.string.login_to_profile);
+ }
+
+ @Override
+ @OnClick(R.id.button)
+ void handleButton() {
+ super.handleButton();
+ login(getUsername(), getPassword());
+ }
+
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderCredentialsBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderCredentialsBaseActivity.java
new file mode 100644
index 00000000..91d0de56
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderCredentialsBaseActivity.java
@@ -0,0 +1,479 @@
+/**
+ * Copyright (c) 2018 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient.providersetup.activities;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import com.google.android.material.textfield.TextInputEditText;
+import com.google.android.material.textfield.TextInputLayout;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import androidx.appcompat.widget.AppCompatButton;
+import androidx.appcompat.widget.AppCompatTextView;
+import android.text.Editable;
+import android.text.Html;
+import android.text.TextWatcher;
+import android.text.method.LinkMovementMethod;
+import android.text.util.Linkify;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.TextView;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import butterknife.InjectView;
+import butterknife.OnClick;
+import se.leap.bitmaskclient.base.models.Constants.CREDENTIAL_ERRORS;
+import se.leap.bitmaskclient.base.models.Provider;
+import se.leap.bitmaskclient.providersetup.ProviderAPI;
+import se.leap.bitmaskclient.providersetup.ProviderAPICommand;
+import se.leap.bitmaskclient.R;
+
+import static android.text.TextUtils.isEmpty;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
+import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_PROVIDER_API_EVENT;
+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.CREDENTIALS_PASSWORD;
+import static se.leap.bitmaskclient.base.models.Constants.CREDENTIALS_USERNAME;
+import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.LOG_IN;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.SIGN_UP;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.USER_MESSAGE;
+
+/**
+ * Base Activity for activities concerning a provider interaction
+ *
+ * Created by fupduck on 09.01.18.
+ */
+
+public abstract class ProviderCredentialsBaseActivity extends ConfigWizardBaseActivity {
+
+ final protected static String TAG = ProviderCredentialsBaseActivity.class.getName();
+
+ final private static String ACTIVITY_STATE = "ACTIVITY STATE";
+
+ final private static String SHOWING_FORM = "SHOWING_FORM";
+ final private static String PERFORMING_ACTION = "PERFORMING_ACTION";
+ final private static String USERNAME_ERROR = "USERNAME_ERROR";
+ final private static String PASSWORD_ERROR = "PASSWORD_ERROR";
+ final private static String PASSWORD_VERIFICATION_ERROR = "PASSWORD_VERIFICATION_ERROR";
+
+ protected Intent mConfigState = new Intent(SHOWING_FORM);
+ protected ProviderAPIBroadcastReceiver providerAPIBroadcastReceiver;
+
+ @InjectView(R.id.provider_credentials_user_message)
+ AppCompatTextView userMessage;
+
+ @InjectView(R.id.provider_credentials_username)
+ TextInputEditText usernameField;
+
+ @InjectView(R.id.provider_credentials_password)
+ TextInputEditText passwordField;
+
+ @InjectView(R.id.provider_credentials_password_verification)
+ TextInputEditText passwordVerificationField;
+
+ @InjectView(R.id.provider_credentials_username_error)
+ TextInputLayout usernameError;
+
+ @InjectView(R.id.provider_credentials_password_error)
+ TextInputLayout passwordError;
+
+ @InjectView(R.id.provider_credentials_password_verification_error)
+ TextInputLayout passwordVerificationError;
+
+ @InjectView(R.id.button)
+ AppCompatButton button;
+
+ private boolean isUsernameError = false;
+ private boolean isPasswordError = false;
+ private boolean isVerificationError = false;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.a_provider_credentials);
+ providerAPIBroadcastReceiver = new ProviderAPIBroadcastReceiver();
+
+ IntentFilter updateIntentFilter = new IntentFilter(BROADCAST_PROVIDER_API_EVENT);
+ updateIntentFilter.addCategory(Intent.CATEGORY_DEFAULT);
+ LocalBroadcastManager.getInstance(this).registerReceiver(providerAPIBroadcastReceiver, updateIntentFilter);
+
+ setUpListeners();
+ restoreState(savedInstanceState);
+
+ String userMessageString = getIntent().getStringExtra(USER_MESSAGE);
+ if (userMessageString != null) {
+ userMessage.setText(userMessageString);
+ userMessage.setVisibility(VISIBLE);
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ String action = mConfigState.getAction();
+ if (action == null) {
+ return;
+ }
+
+ if(action.equalsIgnoreCase(PERFORMING_ACTION)) {
+ showProgressBar();
+ }
+ }
+
+ protected void restoreState(Bundle savedInstance) {
+ super.restoreState(savedInstance);
+ if (savedInstance == null) {
+ return;
+ }
+ if (savedInstance.getString(USER_MESSAGE) != null) {
+ userMessage.setText(savedInstance.getString(USER_MESSAGE));
+ userMessage.setVisibility(VISIBLE);
+ }
+ updateUsernameError(savedInstance.getString(USERNAME_ERROR));
+ updatePasswordError(savedInstance.getString(PASSWORD_ERROR));
+ updateVerificationError(savedInstance.getString(PASSWORD_VERIFICATION_ERROR));
+ if (savedInstance.getString(ACTIVITY_STATE) != null) {
+ mConfigState.setAction(savedInstance.getString(ACTIVITY_STATE));
+ }
+ }
+
+ private void updateUsernameError(String usernameErrorString) {
+ usernameError.setError(usernameErrorString);
+ isUsernameError = usernameErrorString != null;
+ updateButton();
+ }
+
+ private void updatePasswordError(String passwordErrorString) {
+ passwordError.setError(passwordErrorString);
+ isPasswordError = passwordErrorString != null;
+ updateButton();
+ }
+
+ private void updateVerificationError(String verificationErrorString) {
+ passwordVerificationError.setError(verificationErrorString);
+ isVerificationError = verificationErrorString != null;
+ updateButton();
+ }
+
+ private void updateButton() {
+ button.setEnabled(!isPasswordError &&
+ !isUsernameError &&
+ !isVerificationError &&
+ !isEmpty(passwordField.getText()) &&
+ !isEmpty(usernameField.getText()) &&
+ !(passwordVerificationField.getVisibility() == VISIBLE &&
+ getPasswordVerification().length() == 0));
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putString(ACTIVITY_STATE, mConfigState.getAction());
+ if (userMessage.getText() != null && userMessage.getVisibility() == VISIBLE) {
+ outState.putString(USER_MESSAGE, userMessage.getText().toString());
+ }
+ if (usernameError.getError() != null) {
+ outState.putString(USERNAME_ERROR, usernameError.getError().toString());
+ }
+ if (passwordError.getError() != null) {
+ outState.putString(PASSWORD_ERROR, passwordError.getError().toString());
+ }
+ if (passwordVerificationError.getError() != null) {
+ outState.putString(PASSWORD_VERIFICATION_ERROR, passwordVerificationError.getError().toString());
+ }
+
+ super.onSaveInstanceState(outState);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (providerAPIBroadcastReceiver != null)
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(providerAPIBroadcastReceiver);
+ }
+
+ @OnClick(R.id.button)
+ void handleButton() {
+ mConfigState.setAction(PERFORMING_ACTION);
+ hideKeyboard();
+ showProgressBar();
+ }
+
+ protected void setButtonText(@StringRes int buttonText) {
+ button.setText(buttonText);
+ }
+
+ String getUsername() {
+ String username = usernameField.getText().toString();
+ String providerDomain = provider.getDomain();
+ if (username.endsWith(providerDomain)) {
+ try {
+ return username.split("@" + providerDomain)[0];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return "";
+ }
+ }
+ return username;
+ }
+
+ String getPassword() {
+ return passwordField.getText().toString();
+ }
+
+ String getPasswordVerification() {
+ return passwordVerificationField.getText().toString();
+ }
+
+ void login(String username, String password) {
+
+ Bundle parameters = bundleUsernameAndPassword(username, password);
+ ProviderAPICommand.execute(this, LOG_IN, parameters, provider);
+ }
+
+ public void signUp(String username, String password) {
+
+ Bundle parameters = bundleUsernameAndPassword(username, password);
+ ProviderAPICommand.execute(this, SIGN_UP, parameters, provider);
+ }
+
+ void downloadVpnCertificate(Provider handledProvider) {
+ provider = handledProvider;
+ ProviderAPICommand.execute(this, DOWNLOAD_VPN_CERTIFICATE, provider);
+ }
+
+ protected Bundle bundleUsernameAndPassword(String username, String password) {
+ Bundle parameters = new Bundle();
+ if (!username.isEmpty())
+ parameters.putString(CREDENTIALS_USERNAME, username);
+ if (!password.isEmpty())
+ parameters.putString(CREDENTIALS_PASSWORD, password);
+ return parameters;
+ }
+
+ private void setUpListeners() {
+ usernameField.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) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (getUsername().equalsIgnoreCase("")) {
+ s.clear();
+ updateUsernameError(getString(R.string.username_ask));
+ } else {
+ updateUsernameError(null);
+ String suffix = "@" + provider.getDomain();
+ if (!usernameField.getText().toString().endsWith(suffix)) {
+ s.append(suffix);
+ usernameField.setSelection(usernameField.getText().toString().indexOf('@'));
+ }
+ }
+ }
+ });
+ usernameField.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == IME_ACTION_DONE
+ || event != null && event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
+ passwordField.requestFocus();
+ return true;
+ }
+ return false;
+ }
+ });
+
+ passwordField.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) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if(getPassword().length() < 8) {
+ updatePasswordError(getString(R.string.error_not_valid_password_user_message));
+ } else {
+ updatePasswordError(null);
+ }
+ }
+ });
+ passwordField.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == IME_ACTION_DONE
+ || event != null && event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
+ if (passwordVerificationField.getVisibility() == VISIBLE) {
+ passwordVerificationField.requestFocus();
+ } else {
+ button.performClick();
+ }
+ return true;
+ }
+ return false;
+ }
+ });
+
+ passwordVerificationField.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) {
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if(getPassword().equals(getPasswordVerification())) {
+ updateVerificationError(null);
+ } else {
+ updateVerificationError(getString(R.string.password_mismatch));
+ }
+ }
+ });
+ passwordVerificationField.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == IME_ACTION_DONE
+ || event != null && event.getAction() == KeyEvent.ACTION_DOWN
+ && event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
+ button.performClick();
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+
+ private void hideKeyboard() {
+ InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (imm != null) {
+ imm.hideSoftInputFromWindow(passwordField.getWindowToken(), 0);
+ }
+ }
+
+ private void handleReceivedErrors(Bundle arguments) {
+ if (arguments.containsKey(CREDENTIAL_ERRORS.PASSWORD_INVALID_LENGTH.toString())) {
+ updatePasswordError(getString(R.string.error_not_valid_password_user_message));
+ } else if (arguments.containsKey(CREDENTIAL_ERRORS.RISEUP_WARNING.toString())) {
+ userMessage.setVisibility(VISIBLE);
+ userMessage.setText(R.string.login_riseup_warning);
+ }
+ if (arguments.containsKey(CREDENTIALS_USERNAME)) {
+ String username = arguments.getString(CREDENTIALS_USERNAME);
+ usernameField.setText(username);
+ }
+ if (arguments.containsKey(CREDENTIAL_ERRORS.USERNAME_MISSING.toString())) {
+ updateUsernameError(getString(R.string.username_ask));
+ }
+ if (arguments.containsKey(USER_MESSAGE)) {
+ String userMessageString = arguments.getString(USER_MESSAGE);
+ try {
+ userMessageString = new JSONArray(userMessageString).getString(0);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.N) {
+ userMessage.setText(Html.fromHtml(userMessageString, Html.FROM_HTML_MODE_LEGACY));
+ } else {
+ userMessage.setText(Html.fromHtml(userMessageString));
+ }
+ Linkify.addLinks(userMessage, Linkify.ALL);
+ userMessage.setMovementMethod(LinkMovementMethod.getInstance());
+ userMessage.setVisibility(VISIBLE);
+ } else if (userMessage.getVisibility() != GONE) {
+ userMessage.setVisibility(GONE);
+ }
+
+ if (!usernameField.getText().toString().isEmpty() && passwordField.isFocusable())
+ passwordField.requestFocus();
+
+ mConfigState.setAction(SHOWING_FORM);
+ hideProgressBar();
+ }
+
+ private void successfullyFinished(Provider handledProvider) {
+ provider = handledProvider;
+ Intent resultData = new Intent();
+ resultData.putExtra(Provider.KEY, provider);
+ setResult(RESULT_OK, resultData);
+ finish();
+ }
+
+ //TODO: replace with EipSetupObserver
+ public class ProviderAPIBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d(TAG, "received Broadcast");
+
+ String action = intent.getAction();
+ if (action == null || !action.equalsIgnoreCase(BROADCAST_PROVIDER_API_EVENT)) {
+ return;
+ }
+
+ int resultCode = intent.getIntExtra(BROADCAST_RESULT_CODE, RESULT_CANCELED);
+ Bundle resultData = intent.getParcelableExtra(BROADCAST_RESULT_KEY);
+ Provider handledProvider = resultData.getParcelable(PROVIDER_KEY);
+
+ switch (resultCode) {
+ case ProviderAPI.SUCCESSFUL_SIGNUP:
+ String password = resultData.getString(CREDENTIALS_PASSWORD);
+ String username = resultData.getString(CREDENTIALS_USERNAME);
+ login(username, password);
+ break;
+ case ProviderAPI.SUCCESSFUL_LOGIN:
+ downloadVpnCertificate(handledProvider);
+ break;
+ case ProviderAPI.FAILED_LOGIN:
+ case ProviderAPI.FAILED_SIGNUP:
+ handleReceivedErrors((Bundle) intent.getParcelableExtra(BROADCAST_RESULT_KEY));
+ break;
+
+ case ProviderAPI.INCORRECTLY_DOWNLOADED_VPN_CERTIFICATE:
+ // error handling takes place in MainActivity
+ case ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE:
+ successfullyFinished(handledProvider);
+ break;
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderListBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderListBaseActivity.java
new file mode 100644
index 00000000..46a40d11
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderListBaseActivity.java
@@ -0,0 +1,193 @@
+/**
+ * Copyright (c) 2017 LEAP Encryption Access Project and contributors
+ * <p>
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * <p>
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * <p>
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package se.leap.bitmaskclient.providersetup.activities;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.ListView;
+
+import androidx.annotation.NonNull;
+
+import com.pedrogomez.renderers.Renderer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import butterknife.InjectView;
+import butterknife.OnItemClick;
+import se.leap.bitmaskclient.providersetup.AddProviderActivity;
+import se.leap.bitmaskclient.base.models.Provider;
+import se.leap.bitmaskclient.providersetup.ProviderListActivity;
+import se.leap.bitmaskclient.providersetup.ProviderRenderer;
+import se.leap.bitmaskclient.providersetup.ProviderRendererBuilder;
+import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.providersetup.ProviderListAdapter;
+
+import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_ADD_PROVIDER;
+import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_CONFIGURE_LEAP;
+import static se.leap.bitmaskclient.providersetup.ProviderSetupInterface.ProviderConfigState.SETTING_UP_PROVIDER;
+import static se.leap.bitmaskclient.providersetup.ProviderSetupInterface.ProviderConfigState.SHOW_FAILED_DIALOG;
+
+/**
+ * abstract base Activity that builds and shows the list of known available providers.
+ * The implementation of ProviderListBaseActivity differ in that they may or may not allow to bypass
+ * secure download mechanisms including certificate validation.
+ * <p/>
+ * It also allows the user to enter custom providers with a button.
+ *
+ * @author parmegv
+ * @author cyberta
+ */
+
+public abstract class ProviderListBaseActivity extends ProviderSetupBaseActivity {
+
+ @InjectView(R.id.provider_list)
+ protected ListView providerListView;
+ @Inject
+ protected ProviderListAdapter adapter;
+
+ final public static String TAG = ProviderListActivity.class.getSimpleName();
+ final protected static String EXTRAS_KEY_INVALID_URL = "INVALID_URL";
+
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setUpInitialUI();
+ initProviderList();
+ restoreState(savedInstanceState);
+ }
+
+ public abstract void retrySetUpProvider(@NonNull Provider provider);
+
+ protected abstract void onItemSelectedLogic();
+
+ private void initProviderList() {
+ List<Renderer<Provider>> prototypes = new ArrayList<>();
+ prototypes.add(new ProviderRenderer(this));
+ ProviderRendererBuilder providerRendererBuilder = new ProviderRendererBuilder(prototypes);
+ adapter = new ProviderListAdapter(getLayoutInflater(), providerRendererBuilder, getProviderManager());
+ providerListView.setAdapter(adapter);
+ }
+
+ private void setUpInitialUI() {
+ setContentView(R.layout.a_provider_list);
+ setProviderHeaderText(R.string.setup_provider);
+ hideProgressBar();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode == REQUEST_CODE_CONFIGURE_LEAP) {
+ if (resultCode == RESULT_OK) {
+ setResult(resultCode, data);
+ finish();
+ }
+ } else if (requestCode == REQUEST_CODE_ADD_PROVIDER) {
+ if (resultCode == RESULT_OK) {
+ testNewURL = true;
+ String newUrl = data.getStringExtra(AddProviderActivity.EXTRAS_KEY_NEW_URL);
+ this.provider.setMainUrl(newUrl);
+ showAndSelectProvider(newUrl);
+ } else {
+ cancelSettingUpProvider();
+ }
+ }
+ }
+
+ public void showAndSelectProvider(String newURL) {
+ provider = new Provider(newURL, null, null);
+ autoSelectProvider();
+ }
+
+ private void autoSelectProvider() {
+ onItemSelectedLogic();
+ showProgressBar();
+ }
+
+ // ------- ProviderSetupInterface ---v
+ @Override
+ public void handleProviderSetUp(Provider handledProvider) {
+ this.provider = handledProvider;
+ adapter.add(provider);
+ adapter.saveProviders();
+ if (provider.allowsAnonymous()) {
+ //FIXME: providerApiBroadcastReceiver.getConfigState().putExtra(SERVICES_RETRIEVED, true); DEAD CODE???
+ downloadVpnCertificate();
+ } else {
+ showProviderDetails();
+ }
+ }
+
+ @Override
+ public void handleCorrectlyDownloadedCertificate(Provider handledProvider) {
+ this.provider = handledProvider;
+ showProviderDetails();
+ }
+
+ @OnItemClick(R.id.provider_list)
+ void onItemSelected(int position) {
+ if (SETTING_UP_PROVIDER == getConfigState() ||
+ SHOW_FAILED_DIALOG == getConfigState()) {
+ return;
+ }
+
+ //TODO Code 2 pane view
+ provider = adapter.getItem(position);
+ if (provider != null && !provider.isDefault()) {
+ //TODO Code 2 pane view
+ providerConfigState = SETTING_UP_PROVIDER;
+ showProgressBar();
+ onItemSelectedLogic();
+ } else {
+ addAndSelectNewProvider();
+ }
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (SETTING_UP_PROVIDER == providerConfigState ||
+ SHOW_FAILED_DIALOG == providerConfigState) {
+ cancelSettingUpProvider();
+ } else {
+ super.onBackPressed();
+ }
+ }
+
+ /**
+ * Open the new provider dialog
+ */
+ public void addAndSelectNewProvider() {
+ Intent intent = new Intent(this, AddProviderActivity.class);
+ startActivityForResult(intent, REQUEST_CODE_ADD_PROVIDER);
+ }
+
+ /**
+ * Open the new provider dialog
+ */
+ @Override
+ public void addAndSelectNewProvider(String url) {
+ testNewURL = false;
+ Intent intent = new Intent(this, AddProviderActivity.class);
+ intent.putExtra(EXTRAS_KEY_INVALID_URL, url);
+ startActivityForResult(intent, REQUEST_CODE_ADD_PROVIDER);
+ }
+
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderSetupBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderSetupBaseActivity.java
new file mode 100644
index 00000000..e54fb048
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderSetupBaseActivity.java
@@ -0,0 +1,240 @@
+/**
+ * Copyright (c) 2018 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient.providersetup.activities;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.jetbrains.annotations.NotNull;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import se.leap.bitmaskclient.base.FragmentManagerEnhanced;
+import se.leap.bitmaskclient.base.models.Provider;
+import se.leap.bitmaskclient.providersetup.ProviderAPICommand;
+import se.leap.bitmaskclient.providersetup.ProviderDetailActivity;
+import se.leap.bitmaskclient.providersetup.ProviderApiSetupBroadcastReceiver;
+import se.leap.bitmaskclient.providersetup.ProviderManager;
+import se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog;
+import se.leap.bitmaskclient.providersetup.ProviderSetupInterface;
+
+import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_PROVIDER_API_EVENT;
+import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
+import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_CONFIGURE_LEAP;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_PROVIDER_DETAILS;
+import static se.leap.bitmaskclient.providersetup.ProviderSetupInterface.ProviderConfigState.PENDING_SHOW_FAILED_DIALOG;
+import static se.leap.bitmaskclient.providersetup.ProviderSetupInterface.ProviderConfigState.PENDING_SHOW_PROVIDER_DETAILS;
+import static se.leap.bitmaskclient.providersetup.ProviderSetupInterface.ProviderConfigState.PROVIDER_NOT_SET;
+import static se.leap.bitmaskclient.providersetup.ProviderSetupInterface.ProviderConfigState.SETTING_UP_PROVIDER;
+import static se.leap.bitmaskclient.providersetup.ProviderSetupInterface.ProviderConfigState.SHOWING_PROVIDER_DETAILS;
+import static se.leap.bitmaskclient.providersetup.ProviderSetupInterface.ProviderConfigState.SHOW_FAILED_DIALOG;
+
+/**
+ * Created by cyberta on 19.08.18.
+ */
+
+public abstract class ProviderSetupBaseActivity extends ConfigWizardBaseActivity implements ProviderSetupInterface, ProviderSetupFailedDialog.DownloadFailedDialogInterface {
+ final public static String TAG = "PoviderSetupActivity";
+ final private static String ACTIVITY_STATE = "ACTIVITY STATE";
+ final private static String REASON_TO_FAIL = "REASON TO FAIL";
+
+ protected ProviderSetupInterface.ProviderConfigState providerConfigState = PROVIDER_NOT_SET;
+ private ProviderManager providerManager;
+ private FragmentManagerEnhanced fragmentManager;
+
+ private String reasonToFail;
+ protected boolean testNewURL;
+
+ private ProviderApiSetupBroadcastReceiver providerAPIBroadcastReceiver;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ fragmentManager = new FragmentManagerEnhanced(getSupportFragmentManager());
+ providerManager = ProviderManager.getInstance(getAssets(), getExternalFilesDir(null));
+ setUpProviderAPIResultReceiver();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Log.d(TAG, "resuming with ConfigState: " + providerConfigState.toString());
+ if (SETTING_UP_PROVIDER == providerConfigState) {
+ showProgressBar();
+ } else if (PENDING_SHOW_FAILED_DIALOG == providerConfigState) {
+ showProgressBar();
+ showDownloadFailedDialog();
+ } else if (SHOW_FAILED_DIALOG == providerConfigState) {
+ showProgressBar();
+ } else if (SHOWING_PROVIDER_DETAILS == providerConfigState) {
+ cancelSettingUpProvider();
+ } else if (PENDING_SHOW_PROVIDER_DETAILS == providerConfigState) {
+ showProviderDetails();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (providerAPIBroadcastReceiver != null) {
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(providerAPIBroadcastReceiver);
+ }
+ providerAPIBroadcastReceiver = null;
+ }
+
+
+ @Override
+ public void onSaveInstanceState(@NotNull Bundle outState) {
+ outState.putString(ACTIVITY_STATE, providerConfigState.toString());
+ outState.putString(REASON_TO_FAIL, reasonToFail);
+
+ super.onSaveInstanceState(outState);
+ }
+
+ protected FragmentManagerEnhanced getFragmentManagerEnhanced() {
+ return fragmentManager;
+ }
+
+ protected ProviderManager getProviderManager() {
+ return providerManager;
+ }
+
+ protected void setProviderConfigState(ProviderConfigState state) {
+ this.providerConfigState = state;
+ }
+
+ protected void setProvider(Provider provider) {
+ this.provider = provider;
+ }
+
+ // --------- ProviderSetupInterface ---v
+ @Override
+ public Provider getProvider() {
+ return provider;
+ }
+
+ @Override
+ public ProviderConfigState getConfigState() {
+ return providerConfigState;
+ }
+
+ @Override
+ public void handleProviderSetupFailed(Bundle resultData) {
+ reasonToFail = resultData.getString(ERRORS);
+ showDownloadFailedDialog();
+ }
+
+ @Override
+ public void handleIncorrectlyDownloadedCertificate() {
+ cancelSettingUpProvider();
+ setResult(RESULT_CANCELED, new Intent(getConfigState().toString()));
+ }
+
+ // -------- DownloadFailedDialogInterface ---v
+ @Override
+ public void cancelSettingUpProvider() {
+ providerConfigState = PROVIDER_NOT_SET;
+ provider = null;
+ hideProgressBar();
+ }
+
+ @Override
+ public void updateProviderDetails() {
+ providerConfigState = SETTING_UP_PROVIDER;
+ ProviderAPICommand.execute(this, UPDATE_PROVIDER_DETAILS, provider);
+ }
+
+ protected void restoreState(Bundle savedInstanceState) {
+ super.restoreState(savedInstanceState);
+ if (savedInstanceState == null) {
+ return;
+ }
+ this.providerConfigState = ProviderSetupInterface.ProviderConfigState.valueOf(savedInstanceState.getString(ACTIVITY_STATE, PROVIDER_NOT_SET.toString()));
+ if (savedInstanceState.containsKey(REASON_TO_FAIL)) {
+ reasonToFail = savedInstanceState.getString(REASON_TO_FAIL);
+ }
+ }
+
+ private void setUpProviderAPIResultReceiver() {
+ providerAPIBroadcastReceiver = new ProviderApiSetupBroadcastReceiver(this);
+
+ IntentFilter updateIntentFilter = new IntentFilter(BROADCAST_PROVIDER_API_EVENT);
+ updateIntentFilter.addCategory(Intent.CATEGORY_DEFAULT);
+ LocalBroadcastManager.getInstance(this).registerReceiver(providerAPIBroadcastReceiver, updateIntentFilter);
+ }
+
+ /**
+ * Asks ProviderApiService to download an anonymous (anon) VPN certificate.
+ */
+ protected void downloadVpnCertificate() {
+ ProviderAPICommand.execute(this, DOWNLOAD_VPN_CERTIFICATE, provider);
+ }
+
+ /**
+ * Once selected a provider, this fragment offers the user to log in,
+ * use it anonymously (if possible)
+ * or cancel his/her election pressing the back button.
+ */
+ public void showProviderDetails() {
+ // show only if current activity is shown
+ if (isActivityShowing &&
+ providerConfigState != SHOWING_PROVIDER_DETAILS) {
+ providerConfigState = SHOWING_PROVIDER_DETAILS;
+ Intent intent = new Intent(this, ProviderDetailActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ intent.putExtra(PROVIDER_KEY, provider);
+ startActivityForResult(intent, REQUEST_CODE_CONFIGURE_LEAP);
+ } else {
+ providerConfigState = PENDING_SHOW_PROVIDER_DETAILS;
+ }
+ }
+
+ /**
+ * Shows an error dialog, if configuring of a provider failed.
+ */
+ public void showDownloadFailedDialog() {
+ try {
+ providerConfigState = SHOW_FAILED_DIALOG;
+ FragmentTransaction fragmentTransaction = fragmentManager.removePreviousFragment(ProviderSetupFailedDialog.TAG);
+ DialogFragment newFragment;
+ try {
+ JSONObject errorJson = new JSONObject(reasonToFail);
+ newFragment = ProviderSetupFailedDialog.newInstance(provider, errorJson, testNewURL);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ newFragment = ProviderSetupFailedDialog.newInstance(provider, reasonToFail);
+ } catch (NullPointerException e) {
+ //reasonToFail was null
+ return;
+ }
+ newFragment.show(fragmentTransaction, ProviderSetupFailedDialog.TAG);
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ providerConfigState = PENDING_SHOW_FAILED_DIALOG;
+ }
+ }
+
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SignupActivity.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SignupActivity.java
new file mode 100644
index 00000000..c0245845
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SignupActivity.java
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2018 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient.providersetup.activities;
+
+import android.os.Bundle;
+import androidx.annotation.Nullable;
+
+import butterknife.OnClick;
+import se.leap.bitmaskclient.R;
+
+import static android.view.View.VISIBLE;
+
+/**
+ * Create an account with a provider
+ */
+
+public class SignupActivity extends ProviderCredentialsBaseActivity {
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setProviderHeaderLogo(R.drawable.logo);
+ setProviderHeaderText(R.string.create_profile);
+
+ setProgressbarText(R.string.signing_up);
+ setButtonText(R.string.signup_button);
+
+ passwordVerificationField.setVisibility(VISIBLE);
+ passwordVerificationError.setVisibility(VISIBLE);
+ }
+
+ @Override
+ @OnClick(R.id.button)
+ void handleButton() {
+ super.handleButton();
+ if (getPassword().equals(getPasswordVerification())) {
+ signUp(getUsername(), getPassword());
+ }
+ }
+}