diff options
11 files changed, 234 insertions, 57 deletions
diff --git a/app/build.gradle b/app/build.gradle index cb865ecd..b28d9f35 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -77,6 +77,9 @@ dependencies { androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.6.3' testCompile 'junit:junit:4.12' testCompile 'org.json:json:20170516' + debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4' + releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' + betaCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' compile 'com.jakewharton:butterknife:6.1.0' provided 'com.squareup.dagger:dagger-compiler:1.2.2' compile 'com.github.pedrovgs:renderers:1.5' @@ -92,6 +95,17 @@ dependencies { compile 'com.android.support:support-fragment:26.1.0' } +// Ensure the no-op dependency is always used in JVM tests. +configurations.all { config -> + if (config.name.contains('UnitTest')) { + config.resolutionStrategy.eachDependency { details -> + if (details.requested.group == 'com.squareup.leakcanary' && details.requested.name == 'leakcanary-android') { + details.useTarget(group: details.requested.group, name: 'leakcanary-android-no-op', version: details.requested.version) + } + } + } +} + def processFileInplace(file, Closure processText) { def text = file.text file.write(processText(text)) diff --git a/app/src/main/java/se/leap/bitmaskclient/AbstractProviderDetailActivity.java b/app/src/main/java/se/leap/bitmaskclient/AbstractProviderDetailActivity.java index cb5f334b..a596cc7f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/AbstractProviderDetailActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/AbstractProviderDetailActivity.java @@ -21,6 +21,8 @@ import butterknife.InjectView; import static se.leap.bitmaskclient.Constants.PROVIDER_ALLOW_ANONYMOUS; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; +import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; +import static se.leap.bitmaskclient.MainActivity.ACTION_SHOW_VPN_FRAGMENT; public abstract class AbstractProviderDetailActivity extends ConfigWizardBaseActivity { @@ -72,6 +74,7 @@ public abstract class AbstractProviderDetailActivity extends ConfigWizardBaseAct } else { Log.d(TAG, "use anonymously selected"); intent = new Intent(getApplicationContext(), MainActivity.class); + intent.setAction(ACTION_SHOW_VPN_FRAGMENT); } intent.putExtra(PROVIDER_KEY, provider); intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); diff --git a/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java b/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java index 88a01b62..f9e45b79 100644 --- a/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java +++ b/app/src/main/java/se/leap/bitmaskclient/BitmaskApp.java @@ -1,6 +1,10 @@ package se.leap.bitmaskclient; import android.app.Application; +import android.content.Context; + +import com.squareup.leakcanary.LeakCanary; +import com.squareup.leakcanary.RefWatcher; /** * Created by cyberta on 24.10.17. @@ -8,11 +12,31 @@ import android.app.Application; public class BitmaskApp extends Application { + private RefWatcher refWatcher; + @Override public void onCreate() { super.onCreate(); + if (LeakCanary.isInAnalyzerProcess(this)) { + // This process is dedicated to LeakCanary for heap analysis. + // You should not init your app in this process. + return; + } + refWatcher = LeakCanary.install(this); + // Normal app init code...*/ PRNGFixes.apply(); - //TODO: add LeakCanary! } + /** + * Use this method to get a RefWatcher object that checks for memory leaks in the given context. + * Call refWatcher.watch(this) to check if all references get garbage collected. + * @param context + * @return the RefWatcher object + */ + public static RefWatcher getRefWatcher(Context context) { + BitmaskApp application = (BitmaskApp) context.getApplicationContext(); + return application.refWatcher; + } + + } diff --git a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java index 0e861059..ccdf5064 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/ConfigHelper.java @@ -16,6 +16,7 @@ */ package se.leap.bitmaskclient; +import android.content.SharedPreferences; import android.support.annotation.NonNull; import android.util.Log; @@ -28,8 +29,9 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; -import java.io.UnsupportedEncodingException; import java.math.BigInteger; +import java.net.MalformedURLException; +import java.net.URL; import java.security.KeyFactory; import java.security.KeyStore; import java.security.KeyStoreException; @@ -45,6 +47,7 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import static android.R.attr.name; +import static se.leap.bitmaskclient.Constants.PROVIDER_CONFIGURED; /** * Stores constants, and implements auxiliary methods used across all Bitmask Android classes. @@ -256,4 +259,33 @@ public class ConfigHelper { public static KeyStore getKeystore() { return keystore_trusted; } + + + public static String getCurrentProviderName(@NonNull SharedPreferences preferences) { + try { + JSONObject providerDefintion = new JSONObject(preferences.getString(Provider.KEY, "")); + return providerDefintion.getString(Provider.DOMAIN); + } catch (JSONException e) { + e.printStackTrace(); + } + return null; + } + + public static boolean providerInSharedPreferences(@NonNull SharedPreferences preferences) { + return preferences.getBoolean(PROVIDER_CONFIGURED, false); + } + + public static Provider getSavedProviderFromSharedPreferences(@NonNull SharedPreferences preferences) { + Provider provider = new Provider(); + try { + provider.setUrl(new URL(preferences.getString(Provider.MAIN_URL, ""))); + provider.define(new JSONObject(preferences.getString(Provider.KEY, ""))); + provider.setCACert(preferences.getString(Provider.CA_CERT, "")); + } catch (MalformedURLException | JSONException e) { + e.printStackTrace(); + } + + return provider; + } + } diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java index 82ff9db8..5ccb48b5 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java +++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java @@ -18,7 +18,6 @@ package se.leap.bitmaskclient; import android.annotation.SuppressLint; import android.app.AlertDialog; -import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; @@ -42,8 +41,8 @@ import java.util.List; import java.util.Map; import butterknife.InjectView; -import se.leap.bitmaskclient.fragments.AboutFragment; import de.blinkt.openvpn.core.VpnStatus; +import se.leap.bitmaskclient.fragments.AboutFragment; import se.leap.bitmaskclient.userstatus.SessionDialog; import se.leap.bitmaskclient.userstatus.User; import se.leap.bitmaskclient.userstatus.UserStatusFragment; diff --git a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java index 41e496bb..40a9052c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java @@ -1,7 +1,9 @@ package se.leap.bitmaskclient; +import android.content.Intent; import android.os.Bundle; +import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.AppCompatActivity; @@ -16,6 +18,7 @@ public class MainActivity extends AppCompatActivity { private static Provider provider = new Provider(); private static FragmentManagerEnhanced fragmentManager; + public final static String ACTION_SHOW_VPN_FRAGMENT = "action_show_vpn_fragment"; /** * Fragment managing the behaviors, interactions and presentation of the navigation drawer. @@ -37,6 +40,7 @@ public class MainActivity extends AppCompatActivity { R.id.navigation_drawer, (DrawerLayout) findViewById(R.id.drawer_layout)); + handleIntentAction(getIntent()); } public static void sessionDialog(Bundle resultData) { @@ -48,4 +52,34 @@ public class MainActivity extends AppCompatActivity { } } + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + setIntent(intent); + handleIntentAction(intent); + } + + private void handleIntentAction(Intent intent) { + if (intent == null || intent.getAction() == null) { + return; + } + + Fragment fragment = null; + + switch (intent.getAction()) { + case ACTION_SHOW_VPN_FRAGMENT: + fragment = new VpnFragment(); + break; + default: + break; + } + + if (fragment != null) { + fragmentManager.beginTransaction() + .replace(R.id.container, fragment) + .commit(); + } + } + + } diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java index 1d1908a6..67f4e787 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java @@ -23,6 +23,8 @@ import se.leap.bitmaskclient.userstatus.SessionDialog; import se.leap.bitmaskclient.userstatus.SessionDialog.ERRORS; import se.leap.bitmaskclient.userstatus.User; +import static se.leap.bitmaskclient.MainActivity.ACTION_SHOW_VPN_FRAGMENT; + import static android.view.View.GONE; import static android.view.View.VISIBLE; import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE; @@ -359,6 +361,7 @@ public abstract class ProviderCredentialsBaseActivity extends ConfigWizardBaseAc case ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE: intent = new Intent(ProviderCredentialsBaseActivity.this, MainActivity.class); + intent.setAction(ACTION_SHOW_VPN_FRAGMENT); startActivity(intent); //activity.eip_fragment.updateEipService(); break; diff --git a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java index 43d7f152..bb01ddc0 100644 --- a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java @@ -15,7 +15,9 @@ import java.lang.annotation.RetentionPolicy; import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.userstatus.User; +import static se.leap.bitmaskclient.Constants.APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE; import static se.leap.bitmaskclient.Constants.PREFERENCES_APP_VERSION; +import static se.leap.bitmaskclient.Constants.REQUEST_CODE_CONFIGURE_LEAP; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; /** @@ -71,9 +73,8 @@ public class StartActivity extends Activity { VpnStatus.initLogCache(getApplicationContext().getCacheDir()); User.init(getString(R.string.default_username)); - // go to Dashboard - Intent intent = new Intent(this, MainActivity.class); - startActivity(intent); + prepareEIP(); + } /** @@ -141,4 +142,31 @@ public class StartActivity extends Activity { preferences.edit().putInt(PREFERENCES_APP_VERSION, versionCode).apply(); } + private void prepareEIP() { + boolean provider_exists = ConfigHelper.providerInSharedPreferences(preferences); + if (provider_exists) { + Provider provider = ConfigHelper.getSavedProviderFromSharedPreferences(preferences); + if(!provider.isConfigured()) { + configureLeapProvider(); + } else { + Log.d(TAG, "vpn provider is configured"); + + //buildDashboard(getIntent().getBooleanExtra(EIP_RESTART_ON_BOOT, false)); +// user_status_fragment.restoreSessionStatus(savedInstanceState); + Intent intent = new Intent(this, MainActivity.class); + intent.setAction(MainActivity.ACTION_SHOW_VPN_FRAGMENT); + startActivity(intent); + } + } else { + configureLeapProvider(); + } + } + + private void configureLeapProvider() { + if (getIntent().hasExtra(APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE)) { + getIntent().removeExtra(APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE); + } + startActivityForResult(new Intent(this, ConfigurationWizard.class), REQUEST_CODE_CONFIGURE_LEAP); + } + } diff --git a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java index 566134dd..dbe99dce 100644 --- a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java @@ -6,7 +6,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Configuration; import android.os.Bundle; -import android.preference.PreferenceManager; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.view.GravityCompat; @@ -27,15 +26,15 @@ import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.Toast; +import se.leap.bitmaskclient.ConfigHelper; import se.leap.bitmaskclient.ConfigurationWizard; -import se.leap.bitmaskclient.Provider; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.VpnFragment; import se.leap.bitmaskclient.fragments.AboutFragment; import se.leap.bitmaskclient.fragments.LogFragment; -import se.leap.bitmaskclient.userstatus.User; -import se.leap.bitmaskclient.userstatus.UserStatusFragment; +import static android.content.Context.MODE_PRIVATE; +import static se.leap.bitmaskclient.BitmaskApp.getRefWatcher; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_SWITCH_PROVIDER; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; @@ -47,11 +46,6 @@ import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; public class NavigationDrawerFragment extends Fragment { /** - * Remember the position of the selected item. - */ - private static final String STATE_SELECTED_POSITION = "selected_navigation_drawer_position"; - - /** * Per the design guidelines, you should show the drawer on launch until the user manually * expands it. This shared preference tracks this. */ @@ -67,8 +61,8 @@ public class NavigationDrawerFragment extends Fragment { private ListView mDrawerSettingsListView; private ListView mDrawerAccountsListView; private View mFragmentContainerView; + private ArrayAdapter<String> accountListAdapter; - private int mCurrentSelectedPosition = 0; private boolean mFromSavedInstanceState; private boolean mUserLearnedDrawer; @@ -85,20 +79,8 @@ public class NavigationDrawerFragment extends Fragment { // Read in the flag indicating whether or not the user has demonstrated awareness of the // drawer. See PREF_USER_LEARNED_DRAWER for details. - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity()); - mUserLearnedDrawer = sp.getBoolean(PREF_USER_LEARNED_DRAWER, false); - - preferences = getActivity().getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); - - if (savedInstanceState != null) { - mCurrentSelectedPosition = savedInstanceState.getInt(STATE_SELECTED_POSITION); - mFromSavedInstanceState = true; - } - - // Select either the default item (0) or the last selected item. - if (mDrawerSettingsListView != null) { - selectItem(mDrawerSettingsListView, mCurrentSelectedPosition); - } + preferences = getContext().getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); + mUserLearnedDrawer = preferences.getBoolean(PREF_USER_LEARNED_DRAWER, false); } @Override @@ -142,12 +124,10 @@ public class NavigationDrawerFragment extends Fragment { android.R.layout.simple_list_item_activated_1, android.R.id.text1, new String[]{ - getString(R.string.vpn_fragment_title), getString(R.string.switch_provider_menu_option), getString(R.string.log_fragment_title), getString(R.string.about_fragment_title), })); - mDrawerSettingsListView.setItemChecked(mCurrentSelectedPosition, true); mDrawerAccountsListView = mDrawerView.findViewById(R.id.accountList); mDrawerAccountsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @@ -157,14 +137,21 @@ public class NavigationDrawerFragment extends Fragment { } }); - mDrawerAccountsListView.setAdapter(new ArrayAdapter<String>( - actionBar.getThemedContext(), + + + accountListAdapter = new ArrayAdapter<>(actionBar.getThemedContext(), android.R.layout.simple_list_item_activated_1, - android.R.id.text1, - new String[]{ - User.userName(), - })); - mDrawerAccountsListView.setItemChecked(mCurrentSelectedPosition, true); + android.R.id.text1); + + String providerName = ConfigHelper.getCurrentProviderName(preferences); + if (providerName == null) { + //TODO: ADD A header to the ListView containing a useful message. + //TODO 2: disable switchProvider + } else { + accountListAdapter.add(providerName); + } + + mDrawerAccountsListView.setAdapter(accountListAdapter); mFragmentContainerView = activity.findViewById(fragmentId); mDrawerLayout = drawerLayout; @@ -204,9 +191,7 @@ public class NavigationDrawerFragment extends Fragment { // The user manually opened the drawer; store this flag to prevent auto-showing // the navigation drawer automatically in the future. mUserLearnedDrawer = true; - SharedPreferences sp = PreferenceManager - .getDefaultSharedPreferences(getActivity()); - sp.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).apply(); + preferences.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).apply(); } getActivity().invalidateOptionsMenu(); // calls onPrepareOptionsMenu() @@ -228,11 +213,9 @@ public class NavigationDrawerFragment extends Fragment { }); mDrawerLayout.addDrawerListener(mDrawerToggle); - selectItem(mDrawerSettingsListView, 0); } private void selectItem(AdapterView<?> list, int position) { - mCurrentSelectedPosition = position; if (list != null) { ((ListView) list).setItemChecked(position, true); } @@ -255,7 +238,6 @@ public class NavigationDrawerFragment extends Fragment { @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition); } @Override @@ -287,6 +269,12 @@ public class NavigationDrawerFragment extends Fragment { return super.onOptionsItemSelected(item); } + @Override + public void onDestroy() { + super.onDestroy(); + getRefWatcher(getActivity()).watch(this); + } + /** * Per the navigation drawer design guidelines, updates the action bar to show the global app * 'context', rather than just what's in the current screen. @@ -307,31 +295,26 @@ public class NavigationDrawerFragment extends Fragment { Fragment fragment = null; if (parent == mDrawerAccountsListView) { - mTitle = User.userName(); - fragment = new UserStatusFragment(); - Bundle bundle = new Bundle(); - bundle.putBoolean(Provider.ALLOW_REGISTRATION, new Provider().allowsRegistration()); - fragment.setArguments(bundle); + mTitle = getString(R.string.vpn_fragment_title); + fragment = new VpnFragment(); } else { Log.d("Drawer", String.format("Selected position %d", position)); switch (position) { - case 1: + case 0: // TODO STOP VPN // if (provider.hasEIP()) eip_fragment.stopEipIfPossible(); preferences.edit().clear().apply(); startActivityForResult(new Intent(getActivity(), ConfigurationWizard.class), REQUEST_CODE_SWITCH_PROVIDER); break; - case 2: + case 1: mTitle = getString(R.string.log_fragment_title); fragment = new LogFragment(); break; - case 3: + case 2: mTitle = getString(R.string.about_fragment_title); fragment = new AboutFragment(); break; default: - mTitle = getString(R.string.vpn_fragment_title); - fragment = new VpnFragment(); break; } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3b278091..01715c32 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -67,7 +67,7 @@ <string name="eip_status_start_pending">Initiating connection</string> <string name="eip_cancel_connect_title">Cancel connection?</string> <string name="eip_cancel_connect_text">There is a connection attempt in progress. Do you wish to cancel it?</string> - <string name="eip.warning.browser_inconsistency">In order to avoid leaking your personal information, please close your browser and start a private window after disconnecting the Encrypted VPN Internet Access. Thanks.</string> + <string name="eip.warning.browser_inconsistency">Turn off VPN connection? When the VPN is off, you may leak personal information to your Internet provider or local network.</string> <string name="eip_state_not_connected">"Not running! Insecure connection!"</string> <string name="eip_state_connected">Connection Secure.</string> <string name="provider_problem">It seems there is a problem with the provider.</string> diff --git a/app/src/test/java/se/leap/bitmaskclient/ConfigHelperTest.java b/app/src/test/java/se/leap/bitmaskclient/ConfigHelperTest.java new file mode 100644 index 00000000..a9a5733d --- /dev/null +++ b/app/src/test/java/se/leap/bitmaskclient/ConfigHelperTest.java @@ -0,0 +1,57 @@ +package se.leap.bitmaskclient; + +import android.content.SharedPreferences; + +import org.junit.Before; +import org.junit.Test; + +import se.leap.bitmaskclient.testutils.MockSharedPreferences; +import se.leap.bitmaskclient.testutils.TestSetupHelper; + +import static org.junit.Assert.*; +import static se.leap.bitmaskclient.Constants.PROVIDER_CONFIGURED; +import static se.leap.bitmaskclient.testutils.TestSetupHelper.getInputAsString; + +/** + * Created by cyberta on 17.01.18. + */ +public class ConfigHelperTest { + + private SharedPreferences mockPreferences; + + + @Before + public void setup() { + mockPreferences = new MockSharedPreferences(); + } + + @Test + public void providerInSharedPreferences_notInPreferences_returnsFalse() throws Exception { + assertFalse(ConfigHelper.providerInSharedPreferences(mockPreferences)); + } + + @Test + public void providerInSharedPreferences_inPreferences_returnsTrue() throws Exception { + mockPreferences.edit().putBoolean(PROVIDER_CONFIGURED, true).apply(); + assertTrue(ConfigHelper.providerInSharedPreferences(mockPreferences)); + } + + @Test + public void getSavedProviderFromSharedPreferences_notInPreferences_returnsDefaultProvider() throws Exception { + Provider provider = ConfigHelper.getSavedProviderFromSharedPreferences(mockPreferences); + assertFalse(provider.isConfigured()); + } + + @Test + public void getSavedProviderFromSharedPreferences_notInPreferences_returnsConfiguredProvider() throws Exception { + mockPreferences.edit() + .putString(Provider.KEY, getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.json"))) + .putString(Provider.MAIN_URL, "https://riseup.net") + .putString(Provider.CA_CERT, getInputAsString(getClass().getClassLoader().getResourceAsStream("riseup.net.pem"))) + .apply(); + Provider provider = ConfigHelper.getSavedProviderFromSharedPreferences(mockPreferences); + assertTrue(provider.isConfigured()); + } + + +}
\ No newline at end of file |