/** * Copyright (c) 2013 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; import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; import android.app.DialogFragment; import android.app.FragmentTransaction; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; import android.os.Handler; import android.os.ResultReceiver; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.widget.ProgressBar; import android.widget.TextView; import org.jetbrains.annotations.NotNull; import org.json.JSONException; import org.json.JSONObject; import java.net.MalformedURLException; import java.net.URL; import butterknife.ButterKnife; import butterknife.InjectView; import de.blinkt.openvpn.activities.LogWindow; import se.leap.bitmaskclient.eip.Constants; import se.leap.bitmaskclient.eip.EIP; import se.leap.bitmaskclient.eip.EipStatus; /** * The main user facing Activity of Bitmask Android, consisting of status, controls, * and access to preferences. * * @author Sean Leonard * @author parmegv */ public class Dashboard extends Activity implements SessionDialog.SessionDialogInterface, ProviderAPIResultReceiver.Receiver { protected static final int CONFIGURE_LEAP = 0; protected static final int SWITCH_PROVIDER = 1; final public static String TAG = Dashboard.class.getSimpleName(); final public static String SHARED_PREFERENCES = "LEAPPreferences"; final public static String ACTION_QUIT = "quit"; public static final String REQUEST_CODE = "request_code"; public static final String PARAMETERS = "dashboard parameters"; public static final String START_ON_BOOT = "dashboard start on boot"; final public static String ON_BOOT = "dashboard on boot"; public static final String APP_VERSION = "bitmask version"; private static Context app; protected static SharedPreferences preferences; private FragmentManagerEnhanced fragment_manager; @InjectView(R.id.providerName) TextView provider_name; EipFragment eip_fragment; private Provider provider; private static boolean authed_eip; public ProviderAPIResultReceiver providerAPI_result_receiver; @Override protected void onSaveInstanceState(@NotNull Bundle outState) { if(provider != null) outState.putParcelable(Provider.KEY, provider); super.onSaveInstanceState(outState); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); app = this; PRNGFixes.apply(); preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); fragment_manager = new FragmentManagerEnhanced(getFragmentManager()); handleVersion(); provider = getSavedProvider(savedInstanceState); if (provider == null || provider.getName().isEmpty()) startActivityForResult(new Intent(this,ConfigurationWizard.class),CONFIGURE_LEAP); else buildDashboard(getIntent().getBooleanExtra(ON_BOOT, false)); } private Provider getSavedProvider(Bundle savedInstanceState) { Provider provider = null; if(savedInstanceState != null) provider = savedInstanceState.getParcelable(Provider.KEY); else if(preferences.getBoolean(Constants.PROVIDER_CONFIGURED, false)) provider = getSavedProviderFromSharedPreferences(); return provider; } private Provider getSavedProviderFromSharedPreferences() { Provider provider = null; try { provider = new Provider(new URL(preferences.getString(Provider.MAIN_URL, ""))); provider.define(new JSONObject(preferences.getString(Provider.KEY, ""))); } catch (MalformedURLException | JSONException e) { e.printStackTrace(); } return provider; } private void handleVersion() { try { int versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode; int lastDetectedVersion = preferences.getInt(APP_VERSION, 0); preferences.edit().putInt(APP_VERSION, versionCode).apply(); Log.d("Dashboard", "detected version code: " + versionCode); Log.d("Dashboard", "last detected version code: " + lastDetectedVersion); switch(versionCode) { case 91: // 0.6.0 without Bug #5999 case 101: // 0.8.0 if(!preferences.getString(Constants.KEY, "").isEmpty()) { Intent rebuildVpnProfiles = new Intent(getApplicationContext(), EIP.class); rebuildVpnProfiles.setAction(Constants.ACTION_UPDATE_EIP_SERVICE); startService(rebuildVpnProfiles); } break; } } catch (NameNotFoundException e) { Log.d(TAG, "Handle version didn't find any " + getPackageName() + " package"); } } @SuppressLint("CommitPrefEdits") @Override protected void onActivityResult(int requestCode, int resultCode, Intent data){ Log.d(TAG, "onActivityResult: requestCode = " + requestCode); if ( requestCode == CONFIGURE_LEAP || requestCode == SWITCH_PROVIDER) { if ( resultCode == RESULT_OK ) { preferences.edit().putBoolean(Constants.AUTHED_EIP, authed_eip).apply(); updateEipService(); if (data.hasExtra(Provider.KEY)) { provider = data.getParcelableExtra(Provider.KEY); preferences.edit().putBoolean(Constants.PROVIDER_CONFIGURED, true).commit(); preferences.edit().putString(Provider.MAIN_URL, provider.mainUrl().toString()).apply(); preferences.edit().putString(Provider.KEY, provider.definition().toString()).apply(); } buildDashboard(false); invalidateOptionsMenu(); if (data.hasExtra(SessionDialog.TAG)) { logInDialog(Bundle.EMPTY); } } else if (resultCode == RESULT_CANCELED && data.hasExtra(ACTION_QUIT)) { finish(); } else configErrorDialog(); } else if(requestCode == EIP.DISCONNECT) { EipStatus.getInstance().setConnectedOrDisconnected(); } } /** * Dialog shown when encountering a configuration error. Such errors require * reconfiguring LEAP or aborting the application. */ private void configErrorDialog() { AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getAppContext()); alertBuilder.setTitle(getResources().getString(R.string.setup_error_title)); alertBuilder .setMessage(getResources().getString(R.string.setup_error_text)) .setCancelable(false) .setPositiveButton(getResources().getString(R.string.setup_error_configure_button), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { startActivityForResult(new Intent(getAppContext(),ConfigurationWizard.class),CONFIGURE_LEAP); } }) .setNegativeButton(getResources().getString(R.string.setup_error_close_button), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { preferences.edit().remove(Provider.KEY).remove(Constants.PROVIDER_CONFIGURED).apply(); finish(); } }) .show(); } /** * Inflates permanent UI elements of the View and contains logic for what * service dependent UI elements to include. */ private void buildDashboard(boolean hide_and_turn_on_eip) { setContentView(R.layout.dashboard); ButterKnife.inject(this); provider_name.setText(provider.getDomain()); if ( provider.hasEIP()){ fragment_manager.removePreviousFragment(EipFragment.TAG); eip_fragment = new EipFragment(); if (hide_and_turn_on_eip) { preferences.edit().remove(Dashboard.START_ON_BOOT).apply(); Bundle arguments = new Bundle(); arguments.putBoolean(EipFragment.START_ON_BOOT, true); if(eip_fragment != null) eip_fragment.setArguments(arguments); } fragment_manager.replace(R.id.servicesCollection, eip_fragment, EipFragment.TAG); if (hide_and_turn_on_eip) { onBackPressed(); } } } @Override public boolean onPrepareOptionsMenu(Menu menu) { JSONObject provider_json; try { String provider_json_string = preferences.getString(Provider.KEY, ""); if(!provider_json_string.isEmpty()) { provider_json = new JSONObject(provider_json_string); JSONObject service_description = provider_json.getJSONObject(Provider.SERVICE); boolean authed_eip = !LeapSRPSession.getToken().isEmpty(); boolean allow_registered_eip = service_description.getBoolean(Provider.ALLOW_REGISTRATION); preferences.edit().putBoolean(Constants.ALLOWED_REGISTERED, allow_registered_eip).apply(); if(allow_registered_eip) { if(authed_eip) { menu.findItem(R.id.login_button).setVisible(false); menu.findItem(R.id.logout_button).setVisible(true); } else { menu.findItem(R.id.login_button).setVisible(true); menu.findItem(R.id.logout_button).setVisible(false); } menu.findItem(R.id.signup_button).setVisible(true); } } } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } return true; } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.client_dashboard, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item){ Intent intent; switch (item.getItemId()){ case R.id.about_leap: intent = new Intent(this, AboutActivity.class); startActivity(intent); return true; case R.id.log_window: Intent startLW = new Intent(getAppContext(), LogWindow.class); startLW.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); startActivity(startLW); return true; case R.id.switch_provider: if (provider.hasEIP()){ if (preferences.getBoolean(Constants.AUTHED_EIP, false)) { logOut(); } eip_fragment.askToStopEIP(); } preferences.edit().clear().apply(); startActivityForResult(new Intent(this,ConfigurationWizard.class), SWITCH_PROVIDER); return true; case R.id.login_button: logInDialog(Bundle.EMPTY); return true; case R.id.logout_button: logOut(); return true; case R.id.signup_button: signUpDialog(Bundle.EMPTY); return true; default: return super.onOptionsItemSelected(item); } } protected Intent prepareProviderAPICommand() { providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); providerAPI_result_receiver.setReceiver(this); Intent command = new Intent(this, ProviderAPI.class); command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); return command; } /** * Shows the log in dialog. */ public void logInDialog(Bundle resultData) { FragmentTransaction transaction = fragment_manager.removePreviousFragment(SessionDialog.TAG); DialogFragment newFragment = SessionDialog.newInstance(); if(resultData != null && !resultData.isEmpty()) newFragment.setArguments(resultData); newFragment.show(transaction, SessionDialog.TAG); } @Override public void logIn(String username, String password) { Intent provider_API_command = prepareProviderAPICommand(); Bundle parameters = provider_API_command.getExtras().getBundle(ProviderAPI.PARAMETERS); if(parameters == null) parameters = new Bundle(); parameters.putString(SessionDialog.USERNAME, username); parameters.putString(SessionDialog.PASSWORD, password); if(eip_fragment != null) { eip_fragment.progress_bar.setVisibility(ProgressBar.VISIBLE); eip_fragment.status_message.setText(R.string.authenticating_message); } provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); provider_API_command.setAction(ProviderAPI.SRP_AUTH); startService(provider_API_command); } public void cancelLoginOrSignup() { EipStatus.getInstance().setConnectedOrDisconnected(); } /** * Asks ProviderAPI to log out. */ public void logOut() { Intent provider_API_command = prepareProviderAPICommand(); if(eip_fragment != null) { eip_fragment.progress_bar.setVisibility(ProgressBar.VISIBLE); eip_fragment.status_message.setText(R.string.logout_message); } provider_API_command.setAction(ProviderAPI.LOG_OUT); startService(provider_API_command); } /** * Shows the sign up dialog. */ public void signUpDialog(Bundle resultData) { FragmentTransaction transaction = fragment_manager.removePreviousFragment(SessionDialog.TAG); DialogFragment newFragment = SessionDialog.newInstance(); if(resultData != null && !resultData.isEmpty()) { newFragment.setArguments(resultData); } newFragment.show(transaction, SessionDialog.TAG); } @Override public void signUp(String username, String password) { Intent provider_API_command = prepareProviderAPICommand(); Bundle parameters = provider_API_command.getExtras().getBundle(ProviderAPI.PARAMETERS); if(parameters == null) parameters = new Bundle(); parameters.putString(SessionDialog.USERNAME, username); parameters.putString(SessionDialog.PASSWORD, password); if(eip_fragment != null) { eip_fragment.progress_bar.setVisibility(ProgressBar.VISIBLE); eip_fragment.status_message.setText(R.string.signingup_message); } provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); provider_API_command.setAction(ProviderAPI.SRP_REGISTER); startService(provider_API_command); } /** * Asks ProviderAPI to download an authenticated OpenVPN certificate. */ private void downloadAuthedUserCertificate() { providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler()); providerAPI_result_receiver.setReceiver(this); Intent provider_API_command = new Intent(this, ProviderAPI.class); Bundle parameters = new Bundle(); parameters.putString(ConfigurationWizard.TYPE_OF_CERTIFICATE, ConfigurationWizard.AUTHED_CERTIFICATE); provider_API_command.setAction(ProviderAPI.DOWNLOAD_CERTIFICATE); provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); startService(provider_API_command); } @Override public void onReceiveResult(int resultCode, Bundle resultData) { Log.d(TAG, "onReceiveResult"); if(resultCode == ProviderAPI.SUCCESSFUL_SIGNUP) { String username = resultData.getString(SessionDialog.USERNAME); String password = resultData.getString(SessionDialog.PASSWORD); logIn(username, password); } else if(resultCode == ProviderAPI.FAILED_SIGNUP) { changeStatusMessage(resultCode); hideProgressBar(); signUpDialog(resultData); } else if(resultCode == ProviderAPI.SUCCESSFUL_LOGIN) { changeStatusMessage(resultCode); hideProgressBar(); invalidateOptionsMenu(); authed_eip = true; preferences.edit().putBoolean(Constants.AUTHED_EIP, authed_eip).apply(); downloadAuthedUserCertificate(); } else if(resultCode == ProviderAPI.FAILED_LOGIN) { changeStatusMessage(resultCode); hideProgressBar(); logInDialog(resultData); } else if(resultCode == ProviderAPI.SUCCESSFUL_LOGOUT) { changeStatusMessage(resultCode); hideProgressBar(); invalidateOptionsMenu(); authed_eip = false; preferences.edit().putBoolean(Constants.AUTHED_EIP, authed_eip).apply(); } else if(resultCode == ProviderAPI.LOGOUT_FAILED) { changeStatusMessage(resultCode); hideProgressBar(); setResult(RESULT_CANCELED); } else if(resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE) { changeStatusMessage(resultCode); hideProgressBar(); setResult(RESULT_OK); updateEipService(); } else if(resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE) { changeStatusMessage(resultCode); hideProgressBar(); setResult(RESULT_CANCELED); } else if(resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE) { setResult(RESULT_OK); updateEipService(); } else if(resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE) { setResult(RESULT_CANCELED); } } private void updateEipService() { Intent updateEIP = new Intent(getApplicationContext(), EIP.class); updateEIP.setAction(Constants.ACTION_UPDATE_EIP_SERVICE); ResultReceiver receiver = new ResultReceiver(new Handler()) { protected void onReceiveResult(int resultCode, Bundle resultData) { String request = resultData.getString(Constants.REQUEST_TAG); if(request.equalsIgnoreCase(Constants.ACTION_UPDATE_EIP_SERVICE)) { if(resultCode == Activity.RESULT_OK) { if(authed_eip && eip_fragment != null) eip_fragment.startEipFromScratch(); } } } }; //updateEIP.putExtra(Constants.RECEIVER_TAG, receiver); startService(updateEIP); } private void changeStatusMessage(final int previous_result_code) { // TODO Auto-generated method stub ResultReceiver eip_status_receiver = new ResultReceiver(new Handler()){ protected void onReceiveResult(int resultCode, Bundle resultData){ super.onReceiveResult(resultCode, resultData); String request = resultData.getString(Constants.REQUEST_TAG); if (request.equalsIgnoreCase(Constants.ACTION_IS_EIP_RUNNING)){ if (resultCode == Activity.RESULT_OK){ switch(previous_result_code){ case ProviderAPI.SUCCESSFUL_LOGIN: eip_fragment.status_message.setText(R.string.succesful_authentication_message); break; case ProviderAPI.FAILED_LOGIN: eip_fragment.status_message.setText(R.string.authentication_failed_message); break; case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: eip_fragment.status_message.setText(R.string.authed_secured_status); break; case ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE: eip_fragment.status_message.setText(R.string.incorrectly_downloaded_certificate_message); break; case ProviderAPI.SUCCESSFUL_LOGOUT: eip_fragment.status_message.setText(R.string.logged_out_message); break; case ProviderAPI.LOGOUT_FAILED: eip_fragment.status_message.setText(R.string.log_out_failed_message); break; } } else if(resultCode == Activity.RESULT_CANCELED){ switch(previous_result_code){ case ProviderAPI.SUCCESSFUL_LOGIN: eip_fragment.status_message.setText(R.string.succesful_authentication_message); break; case ProviderAPI.FAILED_LOGIN: eip_fragment.status_message.setText(R.string.authentication_failed_message); break; case ProviderAPI.FAILED_SIGNUP: eip_fragment.status_message.setText(R.string.registration_failed_message); break; case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: break; case ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE: eip_fragment.status_message.setText(R.string.incorrectly_downloaded_certificate_message); break; case ProviderAPI.SUCCESSFUL_LOGOUT: eip_fragment.status_message.setText(R.string.logged_out_message); break; case ProviderAPI.LOGOUT_FAILED: eip_fragment.status_message.setText(R.string.log_out_failed_message); break; } } } } }; eipIsRunning(eip_status_receiver); } private void hideProgressBar() { if(eip_fragment != null) { eip_fragment.progress_bar.setProgress(0); eip_fragment.progress_bar.setVisibility(ProgressBar.GONE); } } /** * For retrieving the base application Context in classes that don't extend * Android's Activity class * * @return Application Context as defined by this for Dashboard instance */ public static Context getAppContext() { return app; } @Override public void startActivityForResult(Intent intent, int requestCode) { intent.putExtra(Dashboard.REQUEST_CODE, requestCode); super.startActivityForResult(intent, requestCode); } private void eipIsRunning(ResultReceiver eip_receiver){ // TODO validate "action"...how do we get the list of intent-filters for a class via Android API? Intent eip_intent = new Intent(this, EIP.class); eip_intent.setAction(Constants.ACTION_IS_EIP_RUNNING); eip_intent.putExtra(Constants.RECEIVER_TAG, eip_receiver); startService(eip_intent); } }