/** * 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; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.os.Handler; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentTransaction; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import android.widget.ListView; import com.pedrogomez.renderers.Renderer; import org.jetbrains.annotations.NotNull; import org.json.JSONException; import org.json.JSONObject; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.List; import javax.inject.Inject; import butterknife.InjectView; import butterknife.OnItemClick; import se.leap.bitmaskclient.fragments.AboutFragment; import static se.leap.bitmaskclient.Constants.BROADCAST_PROVIDER_API_EVENT; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_ADD_PROVIDER; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_CONFIGURE_LEAP; import static se.leap.bitmaskclient.ProviderAPI.DOWNLOAD_VPN_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.ERRORS; import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_SET_UP; import static se.leap.bitmaskclient.ProviderAPI.UPDATE_PROVIDER_DETAILS; import static se.leap.bitmaskclient.ProviderSetupInterface.ProviderConfigState.PENDING_SHOW_FAILED_DIALOG; import static se.leap.bitmaskclient.ProviderSetupInterface.ProviderConfigState.PROVIDER_NOT_SET; import static se.leap.bitmaskclient.ProviderSetupInterface.ProviderConfigState.SETTING_UP_PROVIDER; import static se.leap.bitmaskclient.ProviderSetupInterface.ProviderConfigState.SHOWING_PROVIDER_DETAILS; import static se.leap.bitmaskclient.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 ConfigWizardBaseActivity implements ProviderSetupInterface, ProviderSetupFailedDialog.DownloadFailedDialogInterface, ProviderAPIResultReceiver.Receiver { @InjectView(R.id.provider_list) protected ListView providerListView; @Inject protected ProviderListAdapter adapter; private ProviderManager providerManager; final public static String TAG = ProviderListActivity.class.getSimpleName(); final private static String ACTIVITY_STATE = "ACTIVITY STATE"; protected ProviderConfigState providerConfigState = PROVIDER_NOT_SET; final private static String REASON_TO_FAIL = "REASON TO FAIL"; final protected static String EXTRAS_KEY_INVALID_URL = "INVALID_URL"; public ProviderAPIResultReceiver providerAPIResultReceiver; private ProviderApiSetupBroadcastReceiver providerAPIBroadcastReceiver; private FragmentManagerEnhanced fragmentManager; private boolean isActivityShowing; private String reasonToFail; private boolean testNewURL; 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, providerManager); providerListView.setAdapter(adapter); } @Override public void onSaveInstanceState(@NotNull Bundle outState) { outState.putString(ACTIVITY_STATE, providerConfigState.toString()); outState.putString(REASON_TO_FAIL, reasonToFail); super.onSaveInstanceState(outState); } protected void restoreState(Bundle savedInstanceState) { super.restoreState(savedInstanceState); if (savedInstanceState == null) { return; } this.providerConfigState = ProviderConfigState.valueOf(savedInstanceState.getString(ACTIVITY_STATE, PROVIDER_NOT_SET.toString())); if (savedInstanceState.containsKey(REASON_TO_FAIL)) { reasonToFail = savedInstanceState.getString(REASON_TO_FAIL); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); fragmentManager = new FragmentManagerEnhanced(getSupportFragmentManager()); providerManager = ProviderManager.getInstance(getAssets(), getExternalFilesDir(null)); setUpInitialUI(); initProviderList(); restoreState(savedInstanceState); } @Override protected void onResume() { Log.d(TAG, "resuming with ConfigState: " + providerConfigState.toString()); super.onResume(); setUpProviderAPIResultReceiver(); isActivityShowing = true; if (SETTING_UP_PROVIDER == providerConfigState) { showProgressBar(); checkProviderSetUp(); } else if (PENDING_SHOW_FAILED_DIALOG == providerConfigState) { showProgressBar(); showDownloadFailedDialog(); } else if (SHOW_FAILED_DIALOG == providerConfigState) { showProgressBar(); } else if (SHOWING_PROVIDER_DETAILS == providerConfigState) { cancelSettingUpProvider(); } } private void setUpInitialUI() { setContentView(R.layout.a_provider_list); setProviderHeaderText(R.string.setup_provider); hideProgressBar(); } @Override protected void onPause() { super.onPause(); isActivityShowing = false; if (providerAPIBroadcastReceiver != null) LocalBroadcastManager.getInstance(this).unregisterReceiver(providerAPIBroadcastReceiver); } @Override protected void onDestroy() { super.onDestroy(); providerAPIResultReceiver = null; } @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) { try { provider = new Provider(new URL((newURL))); autoSelectProvider(); } catch (MalformedURLException e) { e.printStackTrace(); } } private void autoSelectProvider() { onItemSelectedLogic(); showProgressBar(); } private void setUpProviderAPIResultReceiver() { providerAPIResultReceiver = new ProviderAPIResultReceiver(new Handler(), this); providerAPIBroadcastReceiver = new ProviderApiSetupBroadcastReceiver(this); IntentFilter updateIntentFilter = new IntentFilter(BROADCAST_PROVIDER_API_EVENT); updateIntentFilter.addCategory(Intent.CATEGORY_DEFAULT); LocalBroadcastManager.getInstance(this).registerReceiver(providerAPIBroadcastReceiver, updateIntentFilter); } @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 handleProviderSetupFailed(Bundle resultData) { reasonToFail = resultData.getString(ERRORS); showDownloadFailedDialog(); } @Override public void handleCorrectlyDownloadedCertificate(Provider handledProvider) { this.provider = handledProvider; showProviderDetails(); } @Override public void handleIncorrectlyDownloadedCertificate() { cancelSettingUpProvider(); setResult(RESULT_CANCELED, new Intent(getConfigState().toString())); } public Provider getProvider() { return provider; } public ProviderConfigState getConfigState() { return providerConfigState; } @Override public void onReceiveResult(int resultCode, Bundle resultData) { if (resultCode == ProviderAPI.PROVIDER_OK) { Provider provider = resultData.getParcelable(PROVIDER_KEY); handleProviderSetUp(provider); } else if (resultCode == AboutFragment.VIEWED) { // Do nothing, right now // I need this for CW to wait for the About activity to end before going back to Dashboard. } } @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(); } } @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); } public void checkProviderSetUp() { ProviderAPICommand.execute(this, PROVIDER_SET_UP, provider, providerAPIResultReceiver); } /** * Asks ProviderApiService to download an anonymous (anon) VPN certificate. */ private void downloadVpnCertificate() { ProviderAPICommand.execute(this, DOWNLOAD_VPN_CERTIFICATE, provider); } /** * 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); } /** * 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; } } /** * 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); } } }