From 6b032b751324a30120cfaabe88940f95171df11f Mon Sep 17 00:00:00 2001 From: cyBerta Date: Tue, 29 Dec 2020 00:54:08 +0100 Subject: new year cleanup: restructure messy project --- .../activities/AbstractProviderDetailActivity.java | 109 +++++ .../activities/AddProviderBaseActivity.java | 125 ++++++ .../activities/ButterKnifeActivity.java | 46 ++ .../activities/ConfigWizardBaseActivity.java | 289 +++++++++++++ .../activities/CustomProviderSetupActivity.java | 121 ++++++ .../providersetup/activities/LoginActivity.java | 32 ++ .../ProviderCredentialsBaseActivity.java | 479 +++++++++++++++++++++ .../activities/ProviderListBaseActivity.java | 193 +++++++++ .../activities/ProviderSetupBaseActivity.java | 240 +++++++++++ .../providersetup/activities/SignupActivity.java | 55 +++ 10 files changed, 1689 insertions(+) create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/activities/AbstractProviderDetailActivity.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/activities/AddProviderBaseActivity.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ButterKnifeActivity.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ConfigWizardBaseActivity.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/activities/CustomProviderSetupActivity.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/activities/LoginActivity.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderCredentialsBaseActivity.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderListBaseActivity.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/activities/ProviderSetupBaseActivity.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SignupActivity.java (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup/activities') 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 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 . + */ +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 . + */ +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 . + */ +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 + *

+ * 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 . + */ + +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. + *

+ * 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> 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 . + */ +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 . + */ +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()); + } + } +} -- cgit v1.2.3