/**
* 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.*;
import android.app.*;
import android.content.*;
import android.content.pm.PackageManager.*;
import android.os.*;
import android.util.*;
import android.view.*;
import android.widget.*;
import org.jetbrains.annotations.*;
import org.json.*;
import java.net.*;
import java.util.*;
import butterknife.*;
import de.blinkt.openvpn.activities.*;
import se.leap.bitmaskclient.eip.*;
/**
* 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, Observer {
protected static final int CONFIGURE_LEAP = 0;
protected static final int SWITCH_PROVIDER = 1;
public static final String TAG = Dashboard.class.getSimpleName();
public static final String SHARED_PREFERENCES = "LEAPPreferences";
public static final 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";
public static final 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;
@InjectView(R.id.user_session_status)
TextView user_session_status_text_view;
@InjectView(R.id.user_session_status_progress)
ProgressBar user_session_status_progress_bar;
EipFragment eip_fragment;
private Provider provider;
private UserSessionStatus user_session_status;
public ProviderAPIResultReceiver providerAPI_result_receiver;
private boolean switching_provider;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
app = this;
user_session_status = UserSessionStatus.getInstance();
user_session_status.addObserver(this);
PRNGFixes.apply();
preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE);
fragment_manager = new FragmentManagerEnhanced(getFragmentManager());
handleVersion();
restoreProvider(savedInstanceState);
if (provider == null || provider.getName().isEmpty())
startActivityForResult(new Intent(this, ConfigurationWizard.class), CONFIGURE_LEAP);
else {
buildDashboard(getIntent().getBooleanExtra(ON_BOOT, false));
restoreSessionStatus(savedInstanceState);
}
}
private void restoreProvider(Bundle savedInstanceState) {
if (savedInstanceState != null) {
if (savedInstanceState.containsKey(Provider.KEY))
provider = savedInstanceState.getParcelable(Provider.KEY);
}
if (provider == null && preferences.getBoolean(Constants.PROVIDER_CONFIGURED, false))
provider = getSavedProviderFromSharedPreferences();
}
private void restoreSessionStatus(Bundle savedInstanceState) {
if (savedInstanceState != null)
if (savedInstanceState.containsKey(UserSessionStatus.TAG)) {
UserSessionStatus.SessionStatus status = (UserSessionStatus.SessionStatus) savedInstanceState.getSerializable(UserSessionStatus.TAG);
user_session_status.updateStatus(status);
}
}
@Override
protected void onSaveInstanceState(@NotNull Bundle outState) {
if (provider != null)
outState.putParcelable(Provider.KEY, provider);
if (user_session_status_text_view != null && user_session_status_text_view.getVisibility() == TextView.VISIBLE)
outState.putSerializable(UserSessionStatus.TAG, user_session_status.sessionStatus());
super.onSaveInstanceState(outState);
}
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();
switch (versionCode) {
case 91: // 0.6.0 without Bug #5999
case 101: // 0.8.0
if (!preferences.getString(Constants.KEY, "").isEmpty())
eip_fragment.updateEipService();
break;
}
} catch (NameNotFoundException e) {
Log.d(TAG, "Handle version didn't find any " + getPackageName() + " package");
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == CONFIGURE_LEAP || requestCode == SWITCH_PROVIDER) {
if (resultCode == RESULT_OK && data.hasExtra(Provider.KEY)) {
provider = data.getParcelableExtra(Provider.KEY);
providerToPreferences(provider);
buildDashboard(false);
invalidateOptionsMenu();
if (data.hasExtra(SessionDialog.TAG)) {
sessionDialog(Bundle.EMPTY);
}
} else if (resultCode == RESULT_CANCELED && data.hasExtra(ACTION_QUIT)) {
finish();
} else
configErrorDialog();
} else if (requestCode == EIP.DISCONNECT) {
EipStatus.getInstance().setConnectedOrDisconnected();
}
}
@SuppressLint("CommitPrefEdits")
private void providerToPreferences(Provider provider) {
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();
}
private void configErrorDialog() {
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getContext());
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(getContext(), 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();
}
}
handleNewUserSessionStatus(user_session_status);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (provider.allowsRegistration()) {
menu.findItem(R.id.signup_button).setVisible(true);
boolean logged_in = User.loggedIn();
menu.findItem(R.id.login_button).setVisible(!logged_in);
menu.findItem(R.id.logout_button).setVisible(logged_in);
}
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.client_dashboard, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.about_leap:
showAbout();
return true;
case R.id.log_window:
showLog();
return true;
case R.id.switch_provider:
switching_provider = true;
if (LeapSRPSession.loggedIn()) logOut();
else switchProvider();
return true;
case R.id.login_button:
sessionDialog(Bundle.EMPTY);
return true;
case R.id.logout_button:
logOut();
return true;
case R.id.signup_button:
sessionDialog(Bundle.EMPTY);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
public void showAbout() {
Intent intent = new Intent(this, AboutActivity.class);
startActivity(intent);
}
public void showLog() {
Intent startLW = new Intent(getContext(), LogWindow.class);
startActivity(startLW);
}
@Override
public void signUp(String username, String password) {
User.setUserName(username);
Bundle parameters = bundlePassword(password);
providerApiCommand(parameters, 0, ProviderAPI.SIGN_UP);
}
@Override
public void logIn(String username, String password) {
User.setUserName(username);
Bundle parameters = bundlePassword(password);
providerApiCommand(parameters, 0, ProviderAPI.LOG_IN);
}
public void logOut() {
providerApiCommand(Bundle.EMPTY, 0, ProviderAPI.LOG_OUT);
}
@Override
public void update(Observable observable, Object data) {
if (observable instanceof UserSessionStatus) {
UserSessionStatus status = (UserSessionStatus) observable;
handleNewUserSessionStatus(status);
}
}
private void handleNewUserSessionStatus(UserSessionStatus status) {
user_session_status = status;
if (provider.allowsRegistration()) {
if (user_session_status.inProgress())
showUserSessionProgressBar();
else
hideUserSessionProgressBar();
changeSessionStatusMessage(user_session_status.toString());
invalidateOptionsMenu();
}
}
private void changeSessionStatusMessage(final String message) {
runOnUiThread(new Runnable() {
@Override
public void run() {
user_session_status_text_view.setText(message);
}
});
}
private void showUserSessionProgressBar() {
runOnUiThread(new Runnable() {
@Override
public void run() {
user_session_status_progress_bar.setVisibility(ProgressBar.VISIBLE);
}
});
}
private void hideUserSessionProgressBar() {
runOnUiThread(new Runnable() {
@Override
public void run() {
user_session_status_progress_bar.setVisibility(ProgressBar.GONE);
}
});
}
protected void downloadVpnCertificate() {
boolean is_authenticated = LeapSRPSession.loggedIn();
boolean allowed_anon = preferences.getBoolean(Constants.ALLOWED_ANON, false);
if (allowed_anon || is_authenticated)
providerApiCommand(Bundle.EMPTY, R.string.downloading_certificate_message, ProviderAPI.DOWNLOAD_CERTIFICATE);
else
sessionDialog(Bundle.EMPTY);
}
private Bundle bundlePassword(String password) {
Bundle parameters = new Bundle();
if (!password.isEmpty())
parameters.putString(SessionDialog.PASSWORD, password);
return parameters;
}
protected void providerApiCommand(Bundle parameters, int progressbar_message_resId, String providerApi_action) {
if (eip_fragment != null && progressbar_message_resId != 0) {
eip_fragment.progress_bar.setVisibility(ProgressBar.VISIBLE);
setStatusMessage(progressbar_message_resId);
}
Intent command = prepareProviderAPICommand(parameters, providerApi_action);
startService(command);
}
private Intent prepareProviderAPICommand(Bundle parameters, String action) {
providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler());
providerAPI_result_receiver.setReceiver(this);
Intent command = new Intent(this, ProviderAPI.class);
command.putExtra(ProviderAPI.PARAMETERS, parameters);
command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver);
command.setAction(action);
return command;
}
public void cancelLoginOrSignup() {
EipStatus.getInstance().setConnectedOrDisconnected();
}
public void sessionDialog(Bundle resultData) {
FragmentTransaction transaction = fragment_manager.removePreviousFragment(SessionDialog.TAG);
DialogFragment newFragment = new SessionDialog();
if (provider.getName().equalsIgnoreCase("riseup")) {
resultData = resultData == Bundle.EMPTY ? new Bundle() : resultData;
resultData.putBoolean(SessionDialog.ERRORS.RISEUP_WARNING.toString(), true);
}
if (resultData != null && !resultData.isEmpty()) {
newFragment.setArguments(resultData);
}
newFragment.show(transaction, SessionDialog.TAG);
}
private void switchProvider() {
if (provider.hasEIP()) eip_fragment.stopEipIfPossible();
preferences.edit().clear().apply();
switching_provider = false;
startActivityForResult(new Intent(this, ConfigurationWizard.class), SWITCH_PROVIDER);
}
@Override
public void onReceiveResult(int resultCode, Bundle resultData) {
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) {
sessionDialog(resultData);
} else if (resultCode == ProviderAPI.SUCCESSFUL_LOGIN) {
downloadVpnCertificate();
} else if (resultCode == ProviderAPI.FAILED_LOGIN) {
sessionDialog(resultData);
} else if (resultCode == ProviderAPI.SUCCESSFUL_LOGOUT) {
if (switching_provider) switchProvider();
} else if (resultCode == ProviderAPI.LOGOUT_FAILED) {
setResult(RESULT_CANCELED);
} else if (resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE) {
eip_fragment.updateEipService();
eip_fragment.handleNewVpnCertificate();
setResult(RESULT_OK);
} else if (resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE) {
setResult(RESULT_CANCELED);
} else if (resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE) {
eip_fragment.updateEipService();
setResult(RESULT_OK);
} else if (resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE) {
setResult(RESULT_CANCELED);
}
}
private void setStatusMessage(int string_resId) {
if (eip_fragment != null && eip_fragment.status_message != null)
eip_fragment.status_message.setText(string_resId);
}
public static Context getContext() {
return app;
}
@Override
public void startActivityForResult(Intent intent, int requestCode) {
intent.putExtra(Dashboard.REQUEST_CODE, requestCode);
super.startActivityForResult(intent, requestCode);
}
}