Java tutorial
/* * This file is part of eduVPN. * * eduVPN 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. * * eduVPN 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 eduVPN. If not, see <http://www.gnu.org/licenses/>. */ package nl.eduvpn.app.fragment; import android.animation.ValueAnimator; import android.app.Activity; import android.app.Dialog; import android.app.ProgressDialog; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.helper.ItemTouchHelper; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; import nl.eduvpn.app.BuildConfig; import net.openid.appauth.AuthState; import org.json.JSONObject; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Observer; import javax.inject.Inject; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import butterknife.Unbinder; import de.blinkt.openvpn.VpnProfile; import nl.eduvpn.app.Constants; import nl.eduvpn.app.EduVPNApplication; import nl.eduvpn.app.MainActivity; import nl.eduvpn.app.R; import nl.eduvpn.app.adapter.ProfileAdapter; import nl.eduvpn.app.entity.AuthorizationType; import nl.eduvpn.app.entity.DiscoveredAPI; import nl.eduvpn.app.entity.Instance; import nl.eduvpn.app.entity.KeyPair; import nl.eduvpn.app.entity.Profile; import nl.eduvpn.app.entity.SavedAuthState; import nl.eduvpn.app.entity.SavedKeyPair; import nl.eduvpn.app.entity.SavedProfile; import nl.eduvpn.app.service.APIService; import nl.eduvpn.app.service.ConfigurationService; import nl.eduvpn.app.service.ConnectionService; import nl.eduvpn.app.service.HistoryService; import nl.eduvpn.app.service.PreferencesService; import nl.eduvpn.app.service.SerializerService; import nl.eduvpn.app.service.VPNService; import nl.eduvpn.app.utils.ErrorDialog; import nl.eduvpn.app.utils.FormattingUtils; import nl.eduvpn.app.utils.ItemClickSupport; import nl.eduvpn.app.utils.Log; import nl.eduvpn.app.utils.SwipeToDeleteAnimator; import nl.eduvpn.app.utils.SwipeToDeleteHelper; /** * Fragment which is displayed when the app start. * Created by Daniel Zolnai on 2016-10-20. */ public class HomeFragment extends Fragment { private static final String TAG = HomeFragment.class.getName(); public static final String KEY_SKIP_FIRST_UPDATE = "key_skip_first_update"; @Inject protected HistoryService _historyService; @Inject protected VPNService _vpnService; @Inject protected PreferencesService _preferencesService; @Inject protected APIService _apiService; @Inject protected SerializerService _serializerService; @Inject protected ConnectionService _connectionService; @Inject protected ConfigurationService _configurationService; @BindView(R.id.secureInternetList) protected RecyclerView _secureInternetList; @BindView(R.id.instituteAccessList) protected RecyclerView _instituteAccessList; @BindView(R.id.secureInternetContainer) protected View _secureInternetContainer; @BindView(R.id.instituteAccessContainer) protected View _instituteAccessContainer; @BindView(R.id.noProvidersYet) protected TextView _noProvidersYet; @BindView(R.id.loadingBar) protected ViewGroup _loadingBar; @BindView(R.id.displayText) protected TextView _displayText; @BindView(R.id.warningIcon) protected ImageView _warningIcon; @BindView(R.id.progressBar) protected View _progressBar; private Unbinder _unbinder; private int _pendingInstanceCount; private List<Instance> _problematicInstances; private Observer _newServerObserver; private Dialog _currentDialog; public static HomeFragment newInstance(boolean skipFirstUpdate) { HomeFragment homeFragment = new HomeFragment(); Bundle args = new Bundle(); args.putBoolean(HomeFragment.KEY_SKIP_FIRST_UPDATE, skipFirstUpdate); homeFragment.setArguments(args); return homeFragment; } @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable final Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_home, container, false); _unbinder = ButterKnife.bind(this, view); EduVPNApplication.get(view.getContext()).component().inject(this); // Basic setup of the lists _secureInternetList.setHasFixedSize(true); _instituteAccessList.setHasFixedSize(true); _secureInternetList .setLayoutManager(new LinearLayoutManager(view.getContext(), LinearLayoutManager.VERTICAL, false)); _instituteAccessList .setLayoutManager(new LinearLayoutManager(view.getContext(), LinearLayoutManager.VERTICAL, false)); // Add the adapters ProfileAdapter instituteAccessAdapter = new ProfileAdapter(_historyService, null); _instituteAccessList.setAdapter(instituteAccessAdapter); ProfileAdapter secureInternetAdapter = new ProfileAdapter(_historyService, null); _secureInternetList.setAdapter(secureInternetAdapter); // Swipe to delete ItemTouchHelper instituteSwipeHelper = new ItemTouchHelper(new SwipeToDeleteHelper(getContext())); instituteSwipeHelper.attachToRecyclerView(_instituteAccessList); _instituteAccessList.addItemDecoration(new SwipeToDeleteAnimator(getContext())); ItemTouchHelper secureInternetSwipeHelper = new ItemTouchHelper(new SwipeToDeleteHelper(getContext())); secureInternetSwipeHelper.attachToRecyclerView(_secureInternetList); _secureInternetList.addItemDecoration(new SwipeToDeleteAnimator(getContext())); // Add click listeners ItemClickSupport.OnItemClickListener clickListener = (recyclerView, position, v) -> _onItemClicked(recyclerView, position); ItemClickSupport.addTo(_instituteAccessList).setOnItemClickListener(clickListener); ItemClickSupport.addTo(_secureInternetList).setOnItemClickListener(clickListener); ItemClickSupport.OnItemLongClickListener longClickListener = (recyclerView, position, v) -> _onItemLongClicked(recyclerView, position); ItemClickSupport.addTo(_instituteAccessList).setOnItemLongClickListener(longClickListener); ItemClickSupport.addTo(_secureInternetList).setOnItemLongClickListener(longClickListener); // Fill the lists with data boolean skipUpdate = getArguments() != null && getArguments().containsKey(KEY_SKIP_FIRST_UPDATE) && getArguments().getBoolean(KEY_SKIP_FIRST_UPDATE); if (!skipUpdate) { _updateLists(); } // Add listener on the history service, so we get notified if there is a new server available _historyService.addObserver(_newServerObserver = (observable, o) -> { if (o instanceof Integer) { Integer notificationType = (Integer) o; if (HistoryService.NOTIFICATION_PROFILES_CHANGED.equals(notificationType) || HistoryService.NOTIFICATION_TOKENS_CHANGED.equals(notificationType)) { _updateLists(); } } else { Log.e(TAG, "Unexpected notification type! Live reload might not be working correctly."); } }); return view; } private boolean _onItemLongClicked(RecyclerView recyclerView, int position) { // On long click we show the full name in a toast // Is useful when the names don't fit too well. ProfileAdapter adapter = (ProfileAdapter) recyclerView.getAdapter(); if (adapter.isPendingRemoval(position)) { return true; } Pair<Instance, Profile> instanceProfilePair = adapter.getItem(position); Toast.makeText(getContext(), FormattingUtils.formatProfileName(getContext(), instanceProfilePair.first, instanceProfilePair.second), Toast.LENGTH_SHORT).show(); return true; } private void _onItemClicked(RecyclerView recyclerView, int position) { ProfileAdapter adapter = (ProfileAdapter) recyclerView.getAdapter(); if (adapter.isPendingRemoval(position)) { return; } Pair<Instance, Profile> instanceProfilePair = adapter.getItem(position); // We surely have a discovered API and access token, since we just loaded the list with them Instance instance = instanceProfilePair.first; Profile profile = instanceProfilePair.second; DiscoveredAPI discoveredAPI = _historyService.getCachedDiscoveredAPI(instance.getSanitizedBaseURI()); AuthState authState = _historyService.getCachedAuthState(instance); if (discoveredAPI == null || authState == null) { ErrorDialog.show(getContext(), R.string.error_dialog_title, R.string.cant_connect_application_state_missing); return; } _preferencesService.currentInstance(instance); _preferencesService.storeCurrentDiscoveredAPI(discoveredAPI); _preferencesService.storeCurrentProfile(profile); _preferencesService.storeCurrentAuthState(authState); // Always download a new profile. // Just to be sure, _downloadKeyPairIfNeeded(instance, discoveredAPI, profile, authState); } private void _updateLists() { // Fill with data List<SavedAuthState> savedInstituteAccessTokens = _historyService .getSavedTokensForAuthorizationType(AuthorizationType.LOCAL); List<SavedAuthState> savedSecureInternetTokens = _historyService .getSavedTokensForAuthorizationType(AuthorizationType.DISTRIBUTED); // Secure internet tokens are valid for all other instances. savedSecureInternetTokens = enhanceSecureInternetTokensList(savedSecureInternetTokens); if (savedInstituteAccessTokens.isEmpty() && savedSecureInternetTokens.isEmpty()) { // No saved tokens _loadingBar.setVisibility(View.GONE); _noProvidersYet.setVisibility(View.VISIBLE); _instituteAccessContainer.setVisibility(View.GONE); _secureInternetContainer.setVisibility(View.GONE); } else { _loadingBar.setVisibility(View.VISIBLE); _noProvidersYet.setVisibility(View.GONE); // There are some saved institute access tokens if (!savedInstituteAccessTokens.isEmpty()) { _fillList((ProfileAdapter) _instituteAccessList.getAdapter(), savedInstituteAccessTokens); _instituteAccessContainer.setVisibility(View.VISIBLE); } else { _instituteAccessContainer.setVisibility(View.GONE); } // There are some saved secure internet tokens if (!savedSecureInternetTokens.isEmpty()) { _fillList((ProfileAdapter) _secureInternetList.getAdapter(), savedSecureInternetTokens); _secureInternetContainer.setVisibility(View.VISIBLE); } else { _secureInternetContainer.setVisibility(View.GONE); } } } /** * Creates a list with all distributed auth instances, using the same token. * * @return The list of all distributed auth instances. */ private List<SavedAuthState> enhanceSecureInternetTokensList(List<SavedAuthState> savedSecureInternetTokens) { if (savedSecureInternetTokens == null) { return null; } if (savedSecureInternetTokens.size() == 0) { return savedSecureInternetTokens; } AuthState authState = savedSecureInternetTokens.get(0).getAuthState(); List<Instance> instanceList = _configurationService.getSecureInternetList(); List<SavedAuthState> result = new ArrayList<>(); for (Instance instance : instanceList) { result.add(new SavedAuthState(instance, authState)); } return result; } @Override public void onDestroyView() { super.onDestroyView(); if (_currentDialog != null) { _currentDialog.dismiss(); _currentDialog = null; } if (_newServerObserver != null) { _historyService.deleteObserver(_newServerObserver); } _unbinder.unbind(); } /** * Starts fetching the list of profiles to be displayed. * This will be done from multiple APIs and loaded asynchronously. * While the list is still filling, a loading indicator is shown. When all resources were downloaded, * indicator will be hidden. * * @param adapter The adapter to show the profiles in. * @param instanceAccessTokenPairs Each instance & access token pair. */ private void _fillList(final ProfileAdapter adapter, List<SavedAuthState> instanceAccessTokenPairs) { _pendingInstanceCount = instanceAccessTokenPairs.size(); _problematicInstances = new ArrayList<>(); for (SavedAuthState savedAuthState : instanceAccessTokenPairs) { final Instance instance = savedAuthState.getInstance(); final AuthState authState = savedAuthState.getAuthState(); DiscoveredAPI discoveredAPI = _historyService.getCachedDiscoveredAPI(instance.getSanitizedBaseURI()); if (discoveredAPI != null) { // We got everything, fetch the available profiles. _fetchProfileList(adapter, instance, discoveredAPI, authState); } else { _apiService.getJSON(instance.getSanitizedBaseURI() + Constants.API_DISCOVERY_POSTFIX, authState, new APIService.Callback<JSONObject>() { @Override public void onSuccess(JSONObject result) { try { DiscoveredAPI discoveredAPI = _serializerService .deserializeDiscoveredAPI(result); // Cache the result _historyService.cacheDiscoveredAPI(instance.getSanitizedBaseURI(), discoveredAPI); _fetchProfileList(adapter, instance, discoveredAPI, authState); } catch (SerializerService.UnknownFormatException ex) { Log.e(TAG, "Error parsing discovered API!", ex); _problematicInstances.add(instance); _checkLoadingFinished(adapter); } } @Override public void onError(String errorMessage) { Log.e(TAG, "Error while fetching discovered API: " + errorMessage); _problematicInstances.add(instance); _checkLoadingFinished(adapter); } }); } } } /** * Starts downloading the list of profiles for a single VPN provider. * * @param adapter The adapter to download the data into. * @param instance The VPN provider instance. * @param discoveredAPI The discovered API containing the URLs. * @param authState The access and refresh token for the API. */ private void _fetchProfileList(@NonNull final ProfileAdapter adapter, @NonNull final Instance instance, @NonNull DiscoveredAPI discoveredAPI, @NonNull AuthState authState) { _apiService.getJSON(discoveredAPI.getProfileListEndpoint(), authState, new APIService.Callback<JSONObject>() { @Override public void onSuccess(JSONObject result) { try { List<Profile> profiles = _serializerService.deserializeProfileList(result); List<Pair<Instance, Profile>> newItems = new ArrayList<>(); for (Profile profile : profiles) { newItems.add(new Pair<>(instance, profile)); } adapter.addItemsIfNotAdded(newItems); } catch (SerializerService.UnknownFormatException ex) { _problematicInstances.add(instance); Log.e(TAG, "Error parsing profile list.", ex); } _checkLoadingFinished(adapter); } @Override public void onError(String errorMessage) { _problematicInstances.add(instance); Log.e(TAG, "Error fetching profile list: " + errorMessage); _checkLoadingFinished(adapter); } }); } /** * Checks if the loading has finished. * If yes, it hides the loading animation. * If there were any errors, it will display a warning bar as well. * * @param adapter The adapter which the items are being loaded into. */ private synchronized void _checkLoadingFinished(final ProfileAdapter adapter) { _pendingInstanceCount--; if (_pendingInstanceCount <= 0 && _problematicInstances.size() == 0) { if (_loadingBar == null) { Log.d(TAG, "Layout has been destroyed already."); return; } float startHeight = _loadingBar.getHeight(); ValueAnimator animator = ValueAnimator.ofFloat(startHeight, 0); animator.setDuration(600); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { float fraction = animation.getAnimatedFraction(); float alpha = 1f - fraction; float height = (Float) animation.getAnimatedValue(); if (_loadingBar != null) { _loadingBar.setAlpha(alpha); _loadingBar.getLayoutParams().height = (int) height; _loadingBar.requestLayout(); } } }); animator.start(); } else if (_pendingInstanceCount <= 0) { if (_displayText == null) { Log.d(TAG, "Layout has been destroyed already."); return; } // There are some warnings _displayText.setText(R.string.could_not_fetch_all_profiles); _warningIcon.setVisibility(View.VISIBLE); _progressBar.setVisibility(View.GONE); _loadingBar.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // Display a dialog with all the warnings _currentDialog = ErrorDialog.show(getContext(), getString(R.string.warnings_list), getString(R.string.instance_access_warning_message), new ErrorDialog.InstanceWarningHandler() { @Override public List<Instance> getInstances() { return _problematicInstances; } @Override public void retryInstance(Instance instance) { _warningIcon.setVisibility(View.GONE); _progressBar.setVisibility(View.VISIBLE); _displayText.setText(R.string.loading_available_profiles); SavedAuthState savedAuthState = _historyService.getSavedToken(instance); if (savedAuthState == null) { // Should never happen _currentDialog = ErrorDialog.show(getContext(), R.string.error_dialog_title, R.string.data_removed); } else { // Retry _problematicInstances.remove(instance); _fillList(adapter, Collections.singletonList(savedAuthState)); } } @Override public void loginInstance(final Instance instance) { // Find the auth state for the instance and then retry AuthState authState = _historyService.getCachedAuthState(instance); _apiService.getJSON( instance.getSanitizedBaseURI() + Constants.API_DISCOVERY_POSTFIX, authState, new APIService.Callback<JSONObject>() { @Override public void onSuccess(JSONObject result) { try { DiscoveredAPI discoveredAPI = _serializerService .deserializeDiscoveredAPI(result); // Cache the result _historyService.cacheDiscoveredAPI( instance.getSanitizedBaseURI(), discoveredAPI); _problematicInstances.remove(instance); Activity activity = getActivity(); if (activity != null && !activity.isFinishing()) { _connectionService.initiateConnection(getActivity(), instance, discoveredAPI); } } catch (SerializerService.UnknownFormatException ex) { Log.e(TAG, "Error parsing discovered API!", ex); _currentDialog = ErrorDialog.show(getContext(), R.string.error_dialog_title, R.string.provider_incorrect_format); } } @Override public void onError(String errorMessage) { Log.e(TAG, "Error while fetching discovered API: " + errorMessage); DiscoveredAPI discoveredAPI = _historyService .getCachedDiscoveredAPI(instance.getSanitizedBaseURI()); Activity activity = getActivity(); if (discoveredAPI != null && activity != null && !activity.isFinishing()) { _connectionService.initiateConnection(activity, instance, discoveredAPI); } else { _currentDialog = ErrorDialog.show(getContext(), R.string.error_dialog_title, R.string.provider_not_found_retry); } } }); } @Override public void removeInstance(Instance instance) { _historyService.removeAllDataForInstance(instance); _problematicInstances.remove(instance); getActivity().runOnUiThread(() -> _checkLoadingFinished(adapter)); } }); } }); } } /** * Downloads the key pair if no cached one found. After that it downloads the profile and connects to it. * * @param instance The VPN provider. * @param discoveredAPI The discovered API. * @param profile The profile to download. */ private void _downloadKeyPairIfNeeded(@NonNull final Instance instance, @NonNull final DiscoveredAPI discoveredAPI, @NonNull final Profile profile, @NonNull final AuthState authState) { // First we create a keypair, if there is no saved one yet. SavedKeyPair savedKeyPair = _historyService.getSavedKeyPairForInstance(instance); int dialogMessageRes = savedKeyPair != null ? R.string.vpn_profile_checking_certificate : R.string.vpn_profile_creating_keypair; ProgressDialog progressDialog = ProgressDialog.show(getContext(), getString(R.string.progress_dialog_title), getString(dialogMessageRes), true, false); _currentDialog = progressDialog; if (savedKeyPair != null) { _checkCertificateValidity(instance, discoveredAPI, savedKeyPair, profile, authState, progressDialog); return; } String requestData = "display_name=eduVPN"; try { requestData = "display_name=" + URLEncoder.encode(BuildConfig.CERTIFICATE_DISPLAY_NAME, "UTF-8"); } catch (UnsupportedEncodingException e) { // unable to encode the display name, use default } String createKeyPairEndpoint = discoveredAPI.getCreateKeyPairEndpoint(); _apiService.postResource(createKeyPairEndpoint, requestData, authState, new APIService.Callback<String>() { @Override public void onSuccess(String keyPairJson) { try { KeyPair keyPair = _serializerService.deserializeKeyPair(new JSONObject(keyPairJson)); Log.i(TAG, "Created key pair, is it successful: " + keyPair.isOK()); // Save it for later SavedKeyPair savedKeyPair = new SavedKeyPair(instance, keyPair); _historyService.storeSavedKeyPair(savedKeyPair); _downloadProfileWithKeyPair(instance, discoveredAPI, savedKeyPair, profile, authState, progressDialog); } catch (Exception ex) { progressDialog.dismiss(); if (getActivity() != null) { _currentDialog = ErrorDialog.show(getContext(), R.string.error_dialog_title, getString(R.string.error_parsing_keypair, ex.getMessage())); } Log.e(TAG, "Unable to parse keypair data", ex); } } @Override public void onError(String errorMessage) { progressDialog.dismiss(); _currentDialog = ErrorDialog.show(getContext(), R.string.error_dialog_title, getString(R.string.error_creating_keypair, errorMessage)); Log.e(TAG, "Error creating keypair: " + errorMessage); } }); } /** * Now that we have the key pair, we can download the profile. * * @param instance The API instance definition. * @param discoveredAPI The discovered API URLs. * @param savedKeyPair The private key and certificate used to generate the profile. * @param profile The profile to create. * @param dialog Loading dialog which should be dismissed when finished. */ private void _downloadProfileWithKeyPair(final Instance instance, DiscoveredAPI discoveredAPI, final SavedKeyPair savedKeyPair, final Profile profile, final AuthState authState, final ProgressDialog dialog) { dialog.setMessage(getString(R.string.vpn_profile_download_message)); String requestData = "?profile_id=" + profile.getProfileId(); _apiService.getString(discoveredAPI.getProfileConfigEndpoint() + requestData, authState, new APIService.Callback<String>() { @Override public void onSuccess(String vpnConfig) { // The downloaded profile misses the <cert> and <key> fields. We will insert that via the saved key pair. String configName = FormattingUtils.formatProfileName(getContext(), instance, profile); VpnProfile vpnProfile = _vpnService.importConfig(vpnConfig, configName, savedKeyPair); if (vpnProfile != null && getActivity() != null) { // Cache the profile SavedProfile savedProfile = new SavedProfile(instance, profile, vpnProfile.getUUIDString()); _historyService.cacheSavedProfile(savedProfile); // Connect with the profile dialog.dismiss(); _vpnService.connect(getActivity(), vpnProfile); ((MainActivity) getActivity()).openFragment(new ConnectionStatusFragment(), false); } else { dialog.dismiss(); _currentDialog = ErrorDialog.show(getContext(), R.string.error_dialog_title, R.string.error_importing_profile); } } @Override public void onError(String errorMessage) { dialog.dismiss(); _currentDialog = ErrorDialog.show(getContext(), R.string.error_dialog_title, getString(R.string.error_fetching_profile, errorMessage)); Log.e(TAG, "Error fetching profile: " + errorMessage); } }); } private void _checkCertificateValidity(Instance instance, DiscoveredAPI discoveredAPI, SavedKeyPair savedKeyPair, Profile profile, AuthState authState, ProgressDialog dialog) { String commonName = savedKeyPair.getKeyPair().getCertificateCommonName(); if (commonName == null) { // Unable to check, better download it again. _historyService.removeSavedKeyPairs(instance); // Try downloading it again. dialog.dismiss(); _downloadKeyPairIfNeeded(instance, discoveredAPI, profile, authState); Log.w(TAG, "Could not check if certificate is valid. Downloading again."); } _apiService.getJSON(discoveredAPI.getCheckCertificateEndpoint(commonName), authState, new APIService.Callback<JSONObject>() { @Override public void onSuccess(JSONObject result) { try { Boolean isValid = result.getJSONObject("check_certificate").getJSONObject("data") .getBoolean("is_valid"); if (isValid) { Log.i(TAG, "Certificate appears to be valid."); _downloadProfileWithKeyPair(instance, discoveredAPI, savedKeyPair, profile, authState, dialog); } else { dialog.dismiss(); String reason = result.getJSONObject("check_certificate").getJSONObject("data") .getString("reason"); if ("user_disabled".equals(reason) || "certificate_disabled".equals(reason)) { int errorStringId = R.string.error_certificate_disabled; if ("user_disabled".equals(reason)) { errorStringId = R.string.error_user_disabled; } _currentDialog = ErrorDialog.show(getContext(), R.string.error_dialog_title_invalid_certificate, getString(errorStringId)); } else { // Remove stored keypair. _historyService.removeSavedKeyPairs(instance); Log.i(TAG, "Certificate is invalid. Fetching new one. Reason: " + reason); // Try downloading it again. _downloadKeyPairIfNeeded(instance, discoveredAPI, profile, authState); } } } catch (Exception ex) { dialog.dismiss(); _currentDialog = ErrorDialog.show(getContext(), R.string.error_dialog_title, getString(R.string.error_parsing_certificate)); Log.e(TAG, "Unexpected certificate call response!", ex); } } @Override public void onError(String errorMessage) { dialog.dismiss(); if (errorMessage != null && (APIService.USER_NOT_AUTHORIZED_ERROR.equals(errorMessage) || errorMessage.contains("invalid_grant"))) { // Display a dialog with all the warnings _currentDialog = ErrorDialog.show(getContext(), getString(R.string.warnings_list), getString(R.string.instance_access_warning_message), new ErrorDialog.InstanceWarningHandler() { @Override public List<Instance> getInstances() { return Collections.singletonList(instance); } @Override public void retryInstance(Instance instance) { SavedAuthState savedAuthState = _historyService.getSavedToken(instance); if (savedAuthState == null) { // Should never happen _currentDialog = ErrorDialog.show(getContext(), R.string.error_dialog_title, R.string.data_removed); } else { int dialogMessageRes = R.string.vpn_profile_checking_certificate; final ProgressDialog dialog = ProgressDialog.show(getContext(), getString(R.string.progress_dialog_title), getString(dialogMessageRes), true, false); dialog.show(); _currentDialog = dialog; _checkCertificateValidity(instance, discoveredAPI, savedKeyPair, profile, savedAuthState.getAuthState(), dialog); } } @Override public void loginInstance(final Instance instance) { // Find the auth state for the instance and then retry AuthState authState = _historyService.getCachedAuthState(instance); _apiService.getJSON( instance.getSanitizedBaseURI() + Constants.API_DISCOVERY_POSTFIX, authState, new APIService.Callback<JSONObject>() { @Override public void onSuccess(JSONObject result) { try { DiscoveredAPI discoveredAPI = _serializerService .deserializeDiscoveredAPI(result); // Cache the result _historyService.cacheDiscoveredAPI( instance.getSanitizedBaseURI(), discoveredAPI); _connectionService.initiateConnection(getActivity(), instance, discoveredAPI); } catch (SerializerService.UnknownFormatException ex) { Log.e(TAG, "Error parsing discovered API!", ex); _currentDialog = ErrorDialog.show(getContext(), R.string.error_dialog_title, R.string.provider_incorrect_format); } } @Override public void onError(String errorMessage) { Log.e(TAG, "Error while fetching discovered API: " + errorMessage); DiscoveredAPI discoveredAPI = _historyService .getCachedDiscoveredAPI( instance.getSanitizedBaseURI()); Activity activity = getActivity(); if (discoveredAPI != null && activity != null && !activity.isFinishing()) { _connectionService.initiateConnection(activity, instance, discoveredAPI); } else { _currentDialog = ErrorDialog.show(getContext(), R.string.error_dialog_title, R.string.provider_not_found_retry); } } }); } @Override public void removeInstance(Instance instance) { _historyService.removeAllDataForInstance(instance); } }); } else { _currentDialog = ErrorDialog.show(getContext(), R.string.error_dialog_title, getString(R.string.error_checking_certificate)); Log.e(TAG, "Error checking certificate: " + errorMessage); } } }); } /** * Called when the user clicks on the 'Add Provider' button. */ @OnClick(R.id.addProvider) protected void onAddProviderClicked() { if (BuildConfig.API_DISCOVERY_ENABLED) { /* for "basic" we ask for the type of the provider to add */ ((MainActivity) getActivity()).openFragment(new TypeSelectorFragment(), true); } else { /* for "home", i.e. Let's Connect! we immediately ask for the domain */ ((MainActivity) getActivity()).openFragment(new CustomProviderFragment(), true); } } }