diff options
author | cyBerta <cyberta@riseup.net> | 2018-01-17 14:35:48 +0100 |
---|---|---|
committer | cyBerta <cyberta@riseup.net> | 2018-01-17 14:35:48 +0100 |
commit | 20582f79321627257d1b66b22af791e9e22817fd (patch) | |
tree | 0402daf13f51dcf7f9eba319eb8e26dcc2a79d10 /app/src/main | |
parent | 1bb789a9ee39ea8ef652855d8fc4add01848d88f (diff) | |
parent | 58f13c43e70cad2429c9fa10efbc0b6756798800 (diff) |
Merge branch 'fupduck_drawer' into mirror_0xacab_0.9.8
Diffstat (limited to 'app/src/main')
59 files changed, 2789 insertions, 653 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f177f2c7..0a10b13c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,38 +14,35 @@ See the License for the specific language governing permissions and limitations under the License. --> - <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="se.leap.bitmaskclient" android:versionCode="132" android:versionName="0.9.8RC1" > + <uses-sdk + android:minSdkVersion="16" + android:targetSdkVersion="26" /> + <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18"/> - <uses-sdk - android:minSdkVersion="16" - android:targetSdkVersion="26"/> - <application android:name=".BitmaskApp" android:allowBackup="true" android:icon="@drawable/icon" - android:logo="@drawable/icon" android:label="@string/app_name" - android:theme="@style/blinkt"> - + android:logo="@drawable/icon" + android:theme="@style/BitmaskTheme"> <service - android:name="se.leap.bitmaskclient.eip.VoidVpnService" + android:name=".eip.VoidVpnService" android:permission="android.permission.BIND_VPN_SERVICE"> <intent-filter> <action android:name="android.net.VpnService" /> </intent-filter> </service> - <service android:name="de.blinkt.openvpn.core.OpenVPNService" android:permission="android.permission.BIND_VPN_SERVICE"> @@ -53,10 +50,12 @@ <action android:name="android.net.VpnService" /> </intent-filter> </service> - <service android:name="se.leap.bitmaskclient.ProviderAPI" android:enabled="true"/> + <service + android:name=".ProviderAPI" + android:enabled="true" /> <receiver - android:name="se.leap.bitmaskclient.OnBootReceiver" + android:name=".OnBootReceiver" android:enabled="true" android:permission="android.permission.RECEIVE_BOOT_COMPLETED" > <intent-filter android:priority="999"> @@ -64,33 +63,19 @@ </intent-filter> </receiver> - <activity - android:name="se.leap.bitmaskclient.eip.VoidVpnLauncher" - android:theme="@android:style/Theme.Translucent.NoTitleBar" /> - <activity - android:name="de.blinkt.openvpn.activities.DisconnectVPN" /> - + android:name=".eip.VoidVpnLauncher" + android:theme="@android:style/Theme.Translucent.NoTitleBar" /> <activity android:name="de.blinkt.openvpn.LaunchVPN" - android:label="@string/vpn_launch_title" > - </activity> - + android:label="@string/vpn_launch_title" /> <activity - android:name="de.blinkt.openvpn.activities.LogWindow" - android:allowTaskReparenting="true" - android:label="@string/bitmask_log" - android:launchMode="singleTask" /> - - <activity - android:name="se.leap.bitmaskclient.Dashboard" + android:name=".Dashboard" android:label="@string/app_name" - android:uiOptions="splitActionBarWhenNarrow" android:launchMode="singleTop" - > - </activity> + android:uiOptions="splitActionBarWhenNarrow" /> <activity - android:name="se.leap.bitmaskclient.StartActivity" + android:name=".StartActivity" android:label="@string/app_name" android:launchMode="singleTop" android:noHistory="true" @@ -103,17 +88,27 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> + <activity - android:name="se.leap.bitmaskclient.ConfigurationWizard" - android:label="@string/configuration_wizard_title" - android:uiOptions="splitActionBarWhenNarrow" > - </activity> + android:name=".MainActivity" + android:label="@string/title_activity_main" /> + <activity - android:name="se.leap.bitmaskclient.AboutActivity" - android:label="@string/title_about_activity" > - </activity> - - <service android:name="se.leap.bitmaskclient.eip.EIP" android:exported="false"> + android:name=".ConfigurationWizard" + android:label="@string/configuration_wizard_title" /> + + <activity + android:name=".ProviderDetailActivity" + android:label="@string/provider_details_title" + android:launchMode="singleTop" /> + + <activity android:name=".LoginActivity" /> + <activity android:name=".SignupActivity" + android:theme="@style/BitmaskTheme"/> + + <service + android:name=".eip.EIP" + android:exported="false"> <intent-filter> <action android:name="se.leap.bitmaskclient.EIP.UPDATE"/> <action android:name="se.leap.bitmaskclient.EIP.START"/> diff --git a/app/src/main/java/se/leap/bitmaskclient/AboutActivity.java b/app/src/main/java/se/leap/bitmaskclient/AboutActivity.java deleted file mode 100644 index 4ebae32d..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/AboutActivity.java +++ /dev/null @@ -1,35 +0,0 @@ -package se.leap.bitmaskclient; - -import android.app.*; -import android.content.pm.*; -import android.content.pm.PackageManager.*; -import android.os.*; -import android.widget.*; - -public class AboutActivity extends Activity { - - final public static String TAG = "aboutFragment"; - final public static int VIEWED = 0; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.about); - TextView ver = (TextView) findViewById(R.id.version); - - String version; - String name = "Bitmask"; - try { - PackageInfo packageinfo = getPackageManager().getPackageInfo(getPackageName(), 0); - version = packageinfo.versionName; - name = getString(R.string.app_name); - } catch (NameNotFoundException e) { - version = "error fetching version"; - } - - - ver.setText(getString(R.string.version_info, name, version)); - setResult(VIEWED); - } - -} diff --git a/app/src/main/java/se/leap/bitmaskclient/AbstractProviderDetailActivity.java b/app/src/main/java/se/leap/bitmaskclient/AbstractProviderDetailActivity.java new file mode 100644 index 00000000..a92f8f96 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/AbstractProviderDetailActivity.java @@ -0,0 +1,121 @@ +package se.leap.bitmaskclient; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.util.Log; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; + +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; + +public abstract class AbstractProviderDetailActivity extends ButterKnifeActivity { + + final public static String TAG = "providerDetailActivity"; + protected SharedPreferences preferences; + + @InjectView(R.id.provider_detail_domain) + TextView domain; + + @InjectView(R.id.provider_detail_name) + TextView name; + + @InjectView(R.id.provider_detail_description) + TextView description; + + @InjectView(R.id.provider_detail_options) + ListView options; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.provider_detail_fragment); + + preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); + try { + JSONObject providerJson = new JSONObject(preferences.getString(Provider.KEY, "")); + domain.setText(providerJson.getString(Provider.DOMAIN)); + name.setText(providerJson.getJSONObject(Provider.NAME).getString("en")); + description.setText(providerJson.getJSONObject(Provider.DESCRIPTION).getString("en")); + + setTitle(R.string.provider_details_title); + + // Show only the options allowed by the provider + ArrayList<String> optionsList = new ArrayList<>(); + if (registrationAllowed(providerJson)) { + optionsList.add(getString(R.string.login_button)); + optionsList.add(getString(R.string.signup_button)); + } + if (anonAllowed(providerJson)) { + optionsList.add(getString(R.string.use_anonymously_button)); + } + + options.setAdapter(new ArrayAdapter<>( + this, + android.R.layout.simple_list_item_activated_1, + android.R.id.text1, + optionsList.toArray(new String[optionsList.size()]) + )); + options.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + String text = ((TextView) view).getText().toString(); + Intent intent; + if (text.equals(getString(R.string.login_button))) { + Log.d(TAG, "login selected"); + intent = new Intent(getApplicationContext(), LoginActivity.class); + } else if (text.equals(getString(R.string.signup_button))) { + Log.d(TAG, "signup selected"); + intent = new Intent(getApplicationContext(), SignupActivity.class); + } else { + Log.d(TAG, "use anonymously selected"); + intent = new Intent(getApplicationContext(), MainActivity.class); + } + intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + startActivity(intent); + } + }); + } catch (JSONException e) { + // TODO show error and return + } + } + + private boolean anonAllowed(JSONObject providerJson) { + try { + JSONObject serviceDescription = providerJson.getJSONObject(Provider.SERVICE); + return serviceDescription.has(PROVIDER_ALLOW_ANONYMOUS) && serviceDescription.getBoolean(PROVIDER_ALLOW_ANONYMOUS); + } catch (JSONException e) { + return false; + } + } + + private boolean registrationAllowed(JSONObject providerJson) { + try { + JSONObject serviceDescription = providerJson.getJSONObject(Provider.SERVICE); + return serviceDescription.has(Provider.ALLOW_REGISTRATION) && serviceDescription.getBoolean(Provider.ALLOW_REGISTRATION); + } catch (JSONException e) { + return false; + } + } + + @Override + public void onBackPressed() { + SharedPreferences.Editor editor = preferences.edit(); + editor.remove(Provider.KEY).remove(PROVIDER_ALLOW_ANONYMOUS).remove(PROVIDER_KEY).apply(); + super.onBackPressed(); + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/BaseConfigurationWizard.java b/app/src/main/java/se/leap/bitmaskclient/BaseConfigurationWizard.java index 63453ac3..d0868437 100644 --- a/app/src/main/java/se/leap/bitmaskclient/BaseConfigurationWizard.java +++ b/app/src/main/java/se/leap/bitmaskclient/BaseConfigurationWizard.java @@ -17,9 +17,6 @@ package se.leap.bitmaskclient; -import android.app.Activity; -import android.app.DialogFragment; -import android.app.FragmentTransaction; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -27,6 +24,9 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentTransaction; +import android.util.Log; import android.view.Display; import android.view.Menu; import android.view.MenuItem; @@ -48,15 +48,27 @@ import java.util.List; import javax.inject.Inject; -import butterknife.ButterKnife; import butterknife.InjectView; import butterknife.OnItemClick; -import se.leap.bitmaskclient.userstatus.SessionDialog; +import se.leap.bitmaskclient.fragments.AboutFragment; import static android.view.View.GONE; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; +import static se.leap.bitmaskclient.Constants.APP_ACTION_QUIT; +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.ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.ERRORS; +import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE; +import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_API_EVENT; +import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_NOK; +import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_OK; +import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_SET_UP; +import static se.leap.bitmaskclient.ProviderAPI.RESULT_CODE; +import static se.leap.bitmaskclient.ProviderAPI.RESULT_KEY; +import static se.leap.bitmaskclient.ProviderAPI.UPDATE_PROGRESSBAR; /** * abstract base Activity that builds and shows the list of known available providers. @@ -69,44 +81,44 @@ import static se.leap.bitmaskclient.ProviderAPI.ERRORS; * @author cyberta */ -public abstract class BaseConfigurationWizard extends Activity - implements NewProviderDialog.NewProviderDialogInterface, ProviderDetailFragment.ProviderDetailFragmentInterface, DownloadFailedDialog.DownloadFailedDialogInterface, ProviderAPIResultReceiver.Receiver { +public abstract class BaseConfigurationWizard extends ButterKnifeActivity + implements NewProviderDialog.NewProviderDialogInterface, DownloadFailedDialog.DownloadFailedDialogInterface, ProviderAPIResultReceiver.Receiver { @InjectView(R.id.progressbar_configuration_wizard) protected ProgressBar mProgressBar; @InjectView(R.id.progressbar_description) - protected TextView progressbar_description; + protected TextView progressbarDescription; @InjectView(R.id.provider_list) - protected ListView provider_list_view; + protected ListView providerListView; @Inject protected ProviderListAdapter adapter; - private ProviderManager provider_manager; + private ProviderManager providerManager; protected Intent mConfigState = new Intent(PROVIDER_NOT_SET); - protected Provider selected_provider; + protected Provider selectedProvider; final public static String TAG = ConfigurationWizard.class.getSimpleName(); final protected static String PROVIDER_NOT_SET = "PROVIDER NOT SET"; final protected static String SETTING_UP_PROVIDER = "PROVIDER GETS SET"; - final private static String PENDING_SHOW_PROVIDER_DETAILS = "PROVIDER DETAILS SHOWN"; + final private static String SHOWING_PROVIDER_DETAILS = "SHOWING PROVIDER DETAILS"; final private static String PENDING_SHOW_FAILED_DIALOG = "SHOW FAILED DIALOG"; final private static String REASON_TO_FAIL = "REASON TO FAIL"; - final protected static String PROVIDER_SET = "PROVIDER SET"; final protected static String SERVICES_RETRIEVED = "SERVICES RETRIEVED"; final private static String PROGRESSBAR_TEXT = TAG + "PROGRESSBAR_TEXT"; final private static String PROGRESSBAR_NUMBER = TAG + "PROGRESSBAR_NUMBER"; + final private static String ACTIVITY_STATE = "ACTIVITY STATE"; - public ProviderAPIResultReceiver providerAPI_result_receiver; - private ProviderAPIBroadcastReceiver_Update providerAPI_broadcast_receiver_update; + public ProviderAPIResultReceiver providerAPIResultReceiver; + private ProviderAPIBroadcastReceiver providerAPIBroadcastReceiver; protected static SharedPreferences preferences; - FragmentManagerEnhanced fragment_manager; - //TODO: add some states (values for progressbar_text) about ongoing setup or remove that field - private String progressbar_text = ""; - + FragmentManagerEnhanced fragmentManager; + //TODO: add some states (values for progressbarText) about ongoing setup or remove that field + private boolean isActivityShowing; + private String reasonToFail; public abstract void retrySetUpProvider(); @@ -117,26 +129,34 @@ public abstract class BaseConfigurationWizard extends Activity List<Renderer<Provider>> prototypes = new ArrayList<>(); prototypes.add(new ProviderRenderer(this)); ProviderRendererBuilder providerRendererBuilder = new ProviderRendererBuilder(prototypes); - adapter = new ProviderListAdapter(getLayoutInflater(), providerRendererBuilder, provider_manager); - provider_list_view.setAdapter(adapter); + adapter = new ProviderListAdapter(getLayoutInflater(), providerRendererBuilder, providerManager); + providerListView.setAdapter(adapter); } @Override protected void onSaveInstanceState(@NotNull Bundle outState) { if (mProgressBar != null) outState.putInt(PROGRESSBAR_NUMBER, mProgressBar.getProgress()); - if (progressbar_description != null) - outState.putString(PROGRESSBAR_TEXT, progressbar_description.getText().toString()); - outState.putParcelable(Provider.KEY, selected_provider); + if (progressbarDescription != null) + outState.putString(PROGRESSBAR_TEXT, progressbarDescription.getText().toString()); + outState.putString(ACTIVITY_STATE, mConfigState.getAction()); + outState.putParcelable(Provider.KEY, selectedProvider); + + DialogFragment dialogFragment = (DialogFragment) fragmentManager.findFragmentByTag(DownloadFailedDialog.TAG); + if (dialogFragment != null) { + outState.putString(REASON_TO_FAIL, reasonToFail); + dialogFragment.dismiss(); + } + super.onSaveInstanceState(outState); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - preferences = getSharedPreferences(Constants.SHARED_PREFERENCES, MODE_PRIVATE); - fragment_manager = new FragmentManagerEnhanced(getFragmentManager()); - provider_manager = ProviderManager.getInstance(getAssets(), getExternalFilesDir(null)); + preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); + fragmentManager = new FragmentManagerEnhanced(getSupportFragmentManager()); + providerManager = ProviderManager.getInstance(getAssets(), getExternalFilesDir(null)); setUpInitialUI(); @@ -148,107 +168,131 @@ public abstract class BaseConfigurationWizard extends Activity } private void restoreState(Bundle savedInstanceState) { - progressbar_text = savedInstanceState.getString(PROGRESSBAR_TEXT, ""); - selected_provider = savedInstanceState.getParcelable(Provider.KEY); - if (fragment_manager.findFragmentByTag(ProviderDetailFragment.TAG) == null && - (SETTING_UP_PROVIDER.equals(mConfigState.getAction()) || - PENDING_SHOW_PROVIDER_DETAILS.equals(mConfigState.getAction()) || + selectedProvider = savedInstanceState.getParcelable(Provider.KEY); + mConfigState.setAction(savedInstanceState.getString(ACTIVITY_STATE, PROVIDER_NOT_SET)); + + reasonToFail = savedInstanceState.getString(REASON_TO_FAIL); + if(reasonToFail != null) { + showDownloadFailedDialog(); + } + + if (SETTING_UP_PROVIDER.equals(mConfigState.getAction()) || PENDING_SHOW_FAILED_DIALOG.equals(mConfigState.getAction()) - )) { + ) { onItemSelectedUi(); } } @Override protected void onResume() { + Log.d(TAG, "resuming with ConfigState: " + mConfigState.getAction()); super.onResume(); + hideProgressBar(); + isActivityShowing = true; if (SETTING_UP_PROVIDER.equals(mConfigState.getAction())) { showProgressBar(); - adapter.hideAllBut(adapter.indexOf(selected_provider)); - } else if (PENDING_SHOW_PROVIDER_DETAILS.equals(mConfigState.getAction())) { - showProviderDetails(); + adapter.hideAllBut(adapter.indexOf(selectedProvider)); + checkProviderSetUp(); } else if (PENDING_SHOW_FAILED_DIALOG.equals(mConfigState.getAction())) { - showDownloadFailedDialog(mConfigState.getStringExtra(REASON_TO_FAIL)); + showDownloadFailedDialog(); + } else if (SHOWING_PROVIDER_DETAILS.equals(mConfigState.getAction())) { + cancelAndShowAllProviders(); } } private void setUpInitialUI() { setContentView(R.layout.configuration_wizard_activity); - ButterKnife.inject(this); - hideProgressBar(); } private void hideProgressBar() { - //needs to be "INVISIBLE" instead of GONE b/c the progressbar_description gets translated + //needs to be "INVISIBLE" instead of GONE b/c the progressbarDescription gets translated // by the height of mProgressbar (and the height of the first list item) mProgressBar.setVisibility(INVISIBLE); - progressbar_description.setVisibility(INVISIBLE); + progressbarDescription.setVisibility(INVISIBLE); mProgressBar.setProgress(0); } protected void showProgressBar() { mProgressBar.setVisibility(VISIBLE); - progressbar_description.setVisibility(VISIBLE); + progressbarDescription.setVisibility(VISIBLE); + } + + @Override + protected void onPause() { + super.onPause(); + isActivityShowing = false; } @Override protected void onDestroy() { super.onDestroy(); - if (providerAPI_broadcast_receiver_update != null) - unregisterReceiver(providerAPI_broadcast_receiver_update); + if (providerAPIBroadcastReceiver != null) + unregisterReceiver(providerAPIBroadcastReceiver); + providerAPIResultReceiver = null; } private void setUpProviderAPIResultReceiver() { - providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler(), this); - providerAPI_broadcast_receiver_update = new ProviderAPIBroadcastReceiver_Update(); + providerAPIResultReceiver = new ProviderAPIResultReceiver(new Handler(), this); + providerAPIBroadcastReceiver = new ProviderAPIBroadcastReceiver(); - IntentFilter update_intent_filter = new IntentFilter(ProviderAPI.UPDATE_PROGRESSBAR); - update_intent_filter.addCategory(Intent.CATEGORY_DEFAULT); - registerReceiver(providerAPI_broadcast_receiver_update, update_intent_filter); + IntentFilter updateIntentFilter = new IntentFilter(UPDATE_PROGRESSBAR); + updateIntentFilter.addAction(PROVIDER_API_EVENT); + updateIntentFilter.addCategory(Intent.CATEGORY_DEFAULT); + registerReceiver(providerAPIBroadcastReceiver, updateIntentFilter); } - @Override - public void onReceiveResult(int resultCode, Bundle resultData) { - if (resultCode == ProviderAPI.PROVIDER_OK) { - try { - String provider_json_string = preferences.getString(Provider.KEY, ""); - if (!provider_json_string.isEmpty()) - selected_provider.define(new JSONObject(provider_json_string)); - String caCert = preferences.getString(Provider.CA_CERT, ""); - selected_provider.setCACert(caCert); - } catch (JSONException e) { - e.printStackTrace(); - } - - if (preferences.getBoolean(Constants.PROVIDER_ALLOW_ANONYMOUS, false)) { - mConfigState.putExtra(SERVICES_RETRIEVED, true); - - downloadVpnCertificate(); - } else { - mProgressBar.incrementProgressBy(1); - hideProgressBar(); - - showProviderDetails(); - } - } else if (resultCode == ProviderAPI.PROVIDER_NOK) { - mConfigState.setAction(PROVIDER_NOT_SET); - preferences.edit().remove(Provider.KEY).apply(); + void handleProviderSetUp() { + try { + String providerJsonString = preferences.getString(Provider.KEY, ""); + if (!providerJsonString.isEmpty()) + selectedProvider.define(new JSONObject(providerJsonString)); + String caCert = preferences.getString(Provider.CA_CERT, ""); + selectedProvider.setCACert(caCert); + } catch (JSONException e) { + e.printStackTrace(); + } - setResult(RESULT_CANCELED, mConfigState); + if (preferences.getBoolean(PROVIDER_ALLOW_ANONYMOUS, false)) { + mConfigState.putExtra(SERVICES_RETRIEVED, true); - String reason_to_fail = resultData.getString(ERRORS); - showDownloadFailedDialog(reason_to_fail); - } else if (resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE) { + downloadVpnCertificate(); + } else { mProgressBar.incrementProgressBy(1); hideProgressBar(); + showProviderDetails(); - } else if (resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE) { - mConfigState.setAction(PROVIDER_NOT_SET); - hideProgressBar(); - setResult(RESULT_CANCELED, mConfigState); - } else if (resultCode == AboutActivity.VIEWED) { + } + } + + void handleProviderSetupFailed(Bundle resultData) { + mConfigState.setAction(PROVIDER_NOT_SET); + preferences.edit().remove(Provider.KEY).apply(); + + setResult(RESULT_CANCELED, mConfigState); + + reasonToFail = resultData.getString(ERRORS); + showDownloadFailedDialog(); + } + + void handleCorrectlyDownloadedCertificate() { + mProgressBar.incrementProgressBy(1); + hideProgressBar(); + showProviderDetails(); + } + + void handleIncorrectlyDownloadedCertificate() { + mConfigState.setAction(PROVIDER_NOT_SET); + hideProgressBar(); + setResult(RESULT_CANCELED, mConfigState); + } + + @Override + public void onReceiveResult(int resultCode, Bundle resultData) { + if (resultCode == ProviderAPI.PROVIDER_OK) { + handleProviderSetUp(); + } 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. } @@ -257,27 +301,25 @@ public abstract class BaseConfigurationWizard extends Activity @OnItemClick(R.id.provider_list) void onItemSelected(int position) { if (SETTING_UP_PROVIDER.equals(mConfigState.getAction()) || - PENDING_SHOW_PROVIDER_DETAILS.equals(mConfigState.getAction()) || PENDING_SHOW_FAILED_DIALOG.equals(mConfigState.getAction())) { return; } //TODO Code 2 pane view mConfigState.setAction(SETTING_UP_PROVIDER); - selected_provider = adapter.getItem(position); + selectedProvider = adapter.getItem(position); onItemSelectedUi(); onItemSelectedLogic(); } protected void onItemSelectedUi() { - adapter.hideAllBut(adapter.indexOf(selected_provider)); + adapter.hideAllBut(adapter.indexOf(selectedProvider)); startProgressBar(); } @Override public void onBackPressed() { if (SETTING_UP_PROVIDER.equals(mConfigState.getAction()) || - PENDING_SHOW_PROVIDER_DETAILS.equals(mConfigState.getAction()) || PENDING_SHOW_FAILED_DIALOG.equals(mConfigState.getAction())) { stopSettingUpProvider(); } else { @@ -290,7 +332,7 @@ public abstract class BaseConfigurationWizard extends Activity ProviderAPI.stop(); mProgressBar.setVisibility(GONE); mProgressBar.setProgress(0); - progressbar_description.setVisibility(GONE); + progressbarDescription.setVisibility(GONE); cancelSettingUpProvider(); } @@ -300,27 +342,33 @@ public abstract class BaseConfigurationWizard extends Activity hideProgressBar(); mConfigState.setAction(PROVIDER_NOT_SET); adapter.showAllProviders(); - preferences.edit().remove(Provider.KEY).remove(Constants.PROVIDER_ALLOW_ANONYMOUS).remove(Constants.PROVIDER_KEY).apply(); + preferences.edit().remove(Provider.KEY).remove(PROVIDER_ALLOW_ANONYMOUS).remove(PROVIDER_KEY).apply(); } @Override public void updateProviderDetails() { mConfigState.setAction(SETTING_UP_PROVIDER); - Intent provider_API_command = new Intent(this, ProviderAPI.class); + Intent providerAPICommand = new Intent(this, ProviderAPI.class); - provider_API_command.setAction(ProviderAPI.UPDATE_PROVIDER_DETAILS); - provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); + providerAPICommand.setAction(ProviderAPI.UPDATE_PROVIDER_DETAILS); Bundle parameters = new Bundle(); - parameters.putString(Provider.MAIN_URL, selected_provider.getMainUrl().toString()); - provider_API_command.putExtra(ProviderAPI.PARAMETERS, parameters); + parameters.putString(Provider.MAIN_URL, selectedProvider.getMainUrl().toString()); + providerAPICommand.putExtra(ProviderAPI.PARAMETERS, parameters); + + startService(providerAPICommand); + } - startService(provider_API_command); + public void checkProviderSetUp() { + Intent providerAPICommand = new Intent(this, ProviderAPI.class); + providerAPICommand.setAction(PROVIDER_SET_UP); + providerAPICommand.putExtra(ProviderAPI.RECEIVER_KEY, providerAPIResultReceiver); + startService(providerAPICommand); } private void askDashboardToQuitApp() { - Intent ask_quit = new Intent(); - ask_quit.putExtra(Dashboard.ACTION_QUIT, Dashboard.ACTION_QUIT); - setResult(RESULT_CANCELED, ask_quit); + Intent askQuit = new Intent(); + askQuit.putExtra(APP_ACTION_QUIT, APP_ACTION_QUIT); + setResult(RESULT_CANCELED, askQuit); } private void startProgressBar() { @@ -330,11 +378,11 @@ public abstract class BaseConfigurationWizard extends Activity int measured_height = listItemHeight(); mProgressBar.setTranslationY(measured_height); - progressbar_description.setTranslationY(measured_height + mProgressBar.getHeight()); + progressbarDescription.setTranslationY(measured_height + mProgressBar.getHeight()); } private int listItemHeight() { - View listItem = adapter.getView(0, null, provider_list_view); + View listItem = adapter.getView(0, null, providerListView); listItem.setLayoutParams(new RelativeLayout.LayoutParams( RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT)); @@ -355,43 +403,38 @@ public abstract class BaseConfigurationWizard extends Activity * Asks ProviderApiService to download an anonymous (anon) VPN certificate. */ private void downloadVpnCertificate() { - Intent provider_API_command = new Intent(this, ProviderAPI.class); - - provider_API_command.setAction(ProviderAPI.DOWNLOAD_CERTIFICATE); - provider_API_command.putExtra(ProviderAPI.RECEIVER_KEY, providerAPI_result_receiver); - - startService(provider_API_command); + Intent providerAPICommand = new Intent(this, ProviderAPI.class); + providerAPICommand.setAction(ProviderAPI.DOWNLOAD_CERTIFICATE); + startService(providerAPICommand); } /** * Open the new provider dialog */ public void addAndSelectNewProvider() { - FragmentTransaction fragment_transaction = fragment_manager.removePreviousFragment(NewProviderDialog.TAG); - new NewProviderDialog().show(fragment_transaction, NewProviderDialog.TAG); + FragmentTransaction fragmentTransaction = fragmentManager.removePreviousFragment(NewProviderDialog.TAG); + new NewProviderDialog().show(fragmentTransaction, NewProviderDialog.TAG); } /** * Open the new provider dialog with data */ public void addAndSelectNewProvider(String main_url) { - FragmentTransaction fragment_transaction = fragment_manager.removePreviousFragment(NewProviderDialog.TAG); + FragmentTransaction fragmentTransaction = fragmentManager.removePreviousFragment(NewProviderDialog.TAG); DialogFragment newFragment = new NewProviderDialog(); Bundle data = new Bundle(); data.putString(Provider.MAIN_URL, main_url); newFragment.setArguments(data); - newFragment.show(fragment_transaction, NewProviderDialog.TAG); + newFragment.show(fragmentTransaction, NewProviderDialog.TAG); } /** * Shows an error dialog, if configuring of a provider failed. - * - * @param reasonToFail */ - public void showDownloadFailedDialog(String reasonToFail) { + public void showDownloadFailedDialog() { try { - FragmentTransaction fragment_transaction = fragment_manager.removePreviousFragment(DownloadFailedDialog.TAG); + FragmentTransaction fragmentTransaction = fragmentManager.removePreviousFragment(DownloadFailedDialog.TAG); DialogFragment newFragment; try { JSONObject errorJson = new JSONObject(reasonToFail); @@ -400,7 +443,7 @@ public abstract class BaseConfigurationWizard extends Activity e.printStackTrace(); newFragment = DownloadFailedDialog.newInstance(reasonToFail); } - newFragment.show(fragment_transaction, DownloadFailedDialog.TAG); + newFragment.show(fragmentTransaction, DownloadFailedDialog.TAG); } catch (IllegalStateException e) { e.printStackTrace(); mConfigState.setAction(PENDING_SHOW_FAILED_DIALOG); @@ -417,14 +460,12 @@ public abstract class BaseConfigurationWizard extends Activity * */ public void showProviderDetails() { - try { - FragmentTransaction fragment_transaction = fragment_manager.removePreviousFragment(ProviderDetailFragment.TAG); - - DialogFragment newFragment = ProviderDetailFragment.newInstance(); - newFragment.show(fragment_transaction, ProviderDetailFragment.TAG); - } catch (IllegalStateException e) { - e.printStackTrace(); - mConfigState.setAction(PENDING_SHOW_PROVIDER_DETAILS); + // show only if current activity is shown + if (isActivityShowing && !mConfigState.getAction().equalsIgnoreCase(SHOWING_PROVIDER_DETAILS)) { + mConfigState.setAction(SHOWING_PROVIDER_DETAILS); + Intent intent = new Intent(this, ProviderDetailActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + startActivity(intent); } } @@ -438,7 +479,7 @@ public abstract class BaseConfigurationWizard extends Activity public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.about_leap: - startActivityForResult(new Intent(this, AboutActivity.class), 0); + startActivityForResult(new Intent(this, AboutFragment.class), 0); return true; case R.id.new_provider: addAndSelectNewProvider(); @@ -448,37 +489,43 @@ public abstract class BaseConfigurationWizard extends Activity } } - @Override public void cancelAndShowAllProviders() { mConfigState.setAction(PROVIDER_NOT_SET); - selected_provider = null; + selectedProvider = null; adapter.showAllProviders(); } - @Override - public void login() { - mConfigState.setAction(PROVIDER_SET); - Intent ask_login = new Intent(); - ask_login.putExtra(Provider.KEY, selected_provider); - ask_login.putExtra(SessionDialog.TAG, SessionDialog.TAG); - setResult(RESULT_OK, ask_login); - finish(); - } - - @Override - public void use_anonymously() { - mConfigState.setAction(PROVIDER_SET); - Intent pass_provider = new Intent(); - pass_provider.putExtra(Provider.KEY, selected_provider); - setResult(RESULT_OK, pass_provider); - finish(); - } - - public class ProviderAPIBroadcastReceiver_Update extends BroadcastReceiver { + public class ProviderAPIBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - int update = intent.getIntExtra(ProviderAPI.CURRENT_PROGRESS, 0); - mProgressBar.setProgress(update); + String action = intent.getAction(); + + if (action == null) { + return; + } + + if (action.equalsIgnoreCase(UPDATE_PROGRESSBAR)) { + int update = intent.getIntExtra(ProviderAPI.CURRENT_PROGRESS, 0); + mProgressBar.setProgress(update); + } else if (action.equalsIgnoreCase(PROVIDER_API_EVENT)) { + int resultCode = intent.getIntExtra(RESULT_CODE, -1); + + switch (resultCode) { + case PROVIDER_OK: + handleProviderSetUp(); + break; + case PROVIDER_NOK: + handleProviderSetupFailed((Bundle) intent.getParcelableExtra(RESULT_KEY)); + break; + case CORRECTLY_DOWNLOADED_CERTIFICATE: + handleCorrectlyDownloadedCertificate(); + break; + case INCORRECTLY_DOWNLOADED_CERTIFICATE: + handleIncorrectlyDownloadedCertificate(); + break; + + } + } } } } diff --git a/app/src/main/java/se/leap/bitmaskclient/ButterKnifeActivity.java b/app/src/main/java/se/leap/bitmaskclient/ButterKnifeActivity.java new file mode 100644 index 00000000..41164463 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/ButterKnifeActivity.java @@ -0,0 +1,30 @@ +package se.leap.bitmaskclient; + +import android.support.v7.app.AppCompatActivity; +import android.view.View; + +import butterknife.ButterKnife; + +/** + * Automatically inject with ButterKnife after calling setContentView + */ + +public abstract class ButterKnifeActivity extends AppCompatActivity { + + @Override + public void setContentView(View view) { + super.setContentView(view); + ButterKnife.inject(this); + } + + @Override + public void setContentView(int layoutResID) { + super.setContentView(layoutResID); + ButterKnife.inject(this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/Constants.java b/app/src/main/java/se/leap/bitmaskclient/Constants.java index a7ab56fd..7ee3adab 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/Constants.java @@ -10,6 +10,23 @@ public interface Constants { String PREFERENCES_APP_VERSION = "bitmask version"; + ////////////////////////////////////////////// + // REQUEST CODE CONSTANTS + ///////////////////////////////////////////// + + String REQUEST_CODE_KEY = "request_code"; + int REQUEST_CODE_CONFIGURE_LEAP = 0; + int REQUEST_CODE_SWITCH_PROVIDER = 1; + + + ////////////////////////////////////////////// + // APP CONSTANTS + ///////////////////////////////////////////// + + String APP_ACTION_QUIT = "quit"; + String APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE = "configure always-on profile"; + + ////////////////////////////////////////////// // EIP CONSTANTS ///////////////////////////////////////////// @@ -30,7 +47,6 @@ public interface Constants { String EIP_IS_ALWAYS_ON = "EIP.EIP_IS_ALWAYS_ON"; - ////////////////////////////////////////////// // PROVIDER CONSTANTS ///////////////////////////////////////////// diff --git a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java index 755aaf33..82ff9db8 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Dashboard.java +++ b/app/src/main/java/se/leap/bitmaskclient/Dashboard.java @@ -17,9 +17,7 @@ package se.leap.bitmaskclient; import android.annotation.SuppressLint; -import android.app.Activity; import android.app.AlertDialog; -import android.app.FragmentTransaction; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -27,6 +25,7 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; import android.os.Handler; +import android.support.v4.app.FragmentTransaction; import android.util.Log; import android.view.Menu; import android.view.MenuItem; @@ -42,15 +41,25 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import butterknife.ButterKnife; import butterknife.InjectView; +import se.leap.bitmaskclient.fragments.AboutFragment; import de.blinkt.openvpn.core.VpnStatus; import se.leap.bitmaskclient.userstatus.SessionDialog; import se.leap.bitmaskclient.userstatus.User; import se.leap.bitmaskclient.userstatus.UserStatusFragment; +import static se.leap.bitmaskclient.Constants.APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE; +import static se.leap.bitmaskclient.Constants.APP_ACTION_QUIT; import static se.leap.bitmaskclient.Constants.EIP_IS_ALWAYS_ON; import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; +import static se.leap.bitmaskclient.Constants.PREFERENCES_APP_VERSION; +import static se.leap.bitmaskclient.Constants.PROVIDER_ALLOW_ANONYMOUS; +import static se.leap.bitmaskclient.Constants.PROVIDER_CONFIGURED; +import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; +import static se.leap.bitmaskclient.Constants.REQUEST_CODE_CONFIGURE_LEAP; +import static se.leap.bitmaskclient.Constants.REQUEST_CODE_KEY; +import static se.leap.bitmaskclient.Constants.REQUEST_CODE_SWITCH_PROVIDER; +import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; /** * The main user facing Activity of Bitmask Android, consisting of status, controls, @@ -59,13 +68,9 @@ import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; * @author Sean Leonard <meanderingcode@aetherislands.net> * @author parmegv */ -public class Dashboard extends Activity implements ProviderAPIResultReceiver.Receiver { - - protected static final int CONFIGURE_LEAP = 0; - protected static final int SWITCH_PROVIDER = 1; +public class Dashboard extends ButterKnifeActivity { public static final String TAG = Dashboard.class.getSimpleName(); - public static final String ACTION_QUIT = "quit"; /** * When "Disconnect" is clicked from the notification this extra gets added to the calling intent. @@ -73,39 +78,38 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec public static final String ACTION_ASK_TO_CANCEL_VPN = "ask to cancel vpn"; /** * if always-on feature is enabled, but there's no provider configured the EIP Service - * adds this intent extra. ACTION_CONFIGURE_ALWAYS_ON_PROFILE + * adds this intent extra. Constants.APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE * serves to start the Configuration Wizard on top of the Dashboard Activity. */ - public static final String ACTION_CONFIGURE_ALWAYS_ON_PROFILE = "configure always-on profile"; - public static final String REQUEST_CODE = "request_code"; - public static final String PARAMETERS = "dashboard parameters"; - public static final String APP_VERSION = "bitmask version"; - //FIXME: context classes in static fields lead to memory leaks! - private static Context dashboardContext; protected static SharedPreferences preferences; - private FragmentManagerEnhanced fragment_manager; + private static FragmentManagerEnhanced fragment_manager; @InjectView(R.id.providerName) TextView provider_name; - VpnFragment eip_fragment; - UserStatusFragment user_status_fragment; + private VpnFragment eip_fragment; + private UserStatusFragment user_status_fragment; + private static Provider provider = new Provider(); - public ProviderAPIResultReceiver providerAPI_result_receiver; - private boolean switching_provider; + public static ProviderAPIResultReceiver providerAPI_result_receiver; + private static boolean switching_provider; + private boolean handledVersion; + + public static DashboardReceiver dashboardReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - preferences = getSharedPreferences(Constants.SHARED_PREFERENCES, MODE_PRIVATE); - fragment_manager = new FragmentManagerEnhanced(getFragmentManager()); + dashboardReceiver = new DashboardReceiver(this); + preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); + fragment_manager = new FragmentManagerEnhanced(getSupportFragmentManager()); - providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler(), this); + providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler(), dashboardReceiver); - if (dashboardContext == null) { - dashboardContext = this; + if (!handledVersion) { handleVersion(); + handledVersion = true; } // initialize app necessities @@ -141,7 +145,7 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec private boolean providerInSharedPreferences() { return preferences != null && - preferences.getBoolean(Constants.PROVIDER_CONFIGURED, false); + preferences.getBoolean(PROVIDER_CONFIGURED, false); } @@ -171,7 +175,7 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec switch (versionCode) { case 91: // 0.6.0 without Bug #5999 case 101: // 0.8.0 - if (!preferences.getString(Constants.PROVIDER_KEY, "").isEmpty()) + if (!preferences.getString(PROVIDER_KEY, "").isEmpty()) eip_fragment.updateEipService(); break; } @@ -193,15 +197,15 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec } else if (intent.hasExtra(EIP_RESTART_ON_BOOT)) { Log.d(TAG, "Dashboard: EIP_RESTART_ON_BOOT"); prepareEIP(null); - } else if (intent.hasExtra(ACTION_CONFIGURE_ALWAYS_ON_PROFILE)) { - Log.d(TAG, "Dashboard: ACTION_CONFIGURE_ALWAYS_ON_PROFILE"); + } else if (intent.hasExtra(APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE)) { + Log.d(TAG, "Dashboard: Constants.APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE"); handleConfigureAlwaysOn(getIntent()); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == CONFIGURE_LEAP || requestCode == SWITCH_PROVIDER) { + if (requestCode == REQUEST_CODE_CONFIGURE_LEAP || requestCode == REQUEST_CODE_SWITCH_PROVIDER) { if (resultCode == RESULT_OK && data.hasExtra(Provider.KEY)) { provider = data.getParcelableExtra(Provider.KEY); providerToPreferences(provider); @@ -212,7 +216,7 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec sessionDialog(Bundle.EMPTY); } - } else if (resultCode == RESULT_CANCELED && data != null && data.hasExtra(ACTION_QUIT)) { + } else if (resultCode == RESULT_CANCELED && data != null && data.hasExtra(APP_ACTION_QUIT)) { finish(); } else configErrorDialog(); @@ -227,9 +231,9 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec } private void handleConfigureAlwaysOn(Intent intent) { - intent.removeExtra(ACTION_CONFIGURE_ALWAYS_ON_PROFILE); + intent.removeExtra(APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE); Log.d(TAG, "start Configuration wizard!"); - startActivityForResult(new Intent(this, ConfigurationWizard.class), CONFIGURE_LEAP); + startActivityForResult(new Intent(this, ConfigurationWizard.class), REQUEST_CODE_CONFIGURE_LEAP); } private void prepareEIP(Bundle savedInstanceState) { @@ -249,20 +253,20 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec } private void configureLeapProvider() { - if (getIntent().hasExtra(ACTION_CONFIGURE_ALWAYS_ON_PROFILE)) { - getIntent().removeExtra(ACTION_CONFIGURE_ALWAYS_ON_PROFILE); + if (getIntent().hasExtra(APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE)) { + getIntent().removeExtra(APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE); } - startActivityForResult(new Intent(this, ConfigurationWizard.class), CONFIGURE_LEAP); + startActivityForResult(new Intent(this, ConfigurationWizard.class), REQUEST_CODE_CONFIGURE_LEAP); } @SuppressLint("CommitPrefEdits") private void providerToPreferences(Provider provider) { - preferences.edit().putBoolean(Constants.PROVIDER_CONFIGURED, true). + preferences.edit().putBoolean(PROVIDER_CONFIGURED, true). putString(Provider.MAIN_URL, provider.getMainUrl().toString()). putString(Provider.KEY, provider.getDefinition().toString()).apply(); } private void configErrorDialog() { - AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getContext()); + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(this); alertBuilder.setTitle(getResources().getString(R.string.setup_error_title)); alertBuilder .setMessage(getResources().getString(R.string.setup_error_text)) @@ -270,13 +274,13 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec .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); + startActivityForResult(new Intent(Dashboard.this, ConfigurationWizard.class), REQUEST_CODE_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(); + preferences.edit().remove(Provider.KEY).remove(PROVIDER_CONFIGURED).apply(); finish(); } }) @@ -288,11 +292,10 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec * service dependent UI elements to include. */ //TODO: REFACTOR ME! Consider implementing a manager that handles most of VpnFragment's logic about handling EIP commands. - //This way, we could avoid to create UI elements (like fragment_manager.replace(R.id.servicesCollection, eip_fragment, VpnFragment.TAG); ) + //This way, we could avoid to create UI elements (like fragmentManager.replace(R.id.servicesCollection, eip_fragment, VpnFragment.TAG); ) // just to start services and destroy them afterwards private void buildDashboard(boolean hideAndTurnOnEipOnBoot) { setContentView(R.layout.dashboard); - ButterKnife.inject(this); provider_name.setText(provider.getDomain()); @@ -316,9 +319,9 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec * * @param hideAndTurnOnEipOnBoot Flag that indicates if system intent android.intent.action.BOOT_COMPLETED * has caused to start Dashboard - * @return + * @return the created VPNFragment */ - private VpnFragment prepareEipFragment(boolean hideAndTurnOnEipOnBoot) { + public VpnFragment prepareEipFragment(boolean hideAndTurnOnEipOnBoot) { VpnFragment eip_fragment = new VpnFragment(); if (hideAndTurnOnEipOnBoot && !isAlwaysOn()) { @@ -334,7 +337,7 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec /** * checks if Android's VPN feature 'always-on' is enabled for Bitmask - * @return + * @return true if 'always-on' is enabled false if not */ private boolean isAlwaysOn() { return preferences.getBoolean(EIP_IS_ALWAYS_ON, false); @@ -378,25 +381,28 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec } public void showAbout() { - Intent intent = new Intent(this, AboutActivity.class); + Intent intent = new Intent(this, AboutFragment.class); startActivity(intent); } public void showLog() { - LogWindowWrapper log_window_wrapper = LogWindowWrapper.getInstance(getContext()); + LogWindowWrapper log_window_wrapper = LogWindowWrapper.getInstance(this); log_window_wrapper.showLog(); } - public void downloadVpnCertificate() { + + // TODO MOVE TO VPNManager(?) + public static void downloadVpnCertificate() { boolean is_authenticated = User.loggedIn(); - boolean allowed_anon = preferences.getBoolean(Constants.PROVIDER_ALLOW_ANONYMOUS, false); + boolean allowed_anon = preferences.getBoolean(PROVIDER_ALLOW_ANONYMOUS, false); if (allowed_anon || is_authenticated) ProviderAPICommand.execute(Bundle.EMPTY, ProviderAPI.DOWNLOAD_CERTIFICATE, providerAPI_result_receiver); else sessionDialog(Bundle.EMPTY); } - public void sessionDialog(Bundle resultData) { + // TODO how can we replace this + public static void sessionDialog(Bundle resultData) { try { FragmentTransaction transaction = fragment_manager.removePreviousFragment(SessionDialog.TAG); SessionDialog.getInstance(provider, resultData).show(transaction, SessionDialog.TAG); @@ -411,7 +417,7 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec clearDataOfLastProvider(); switching_provider = false; - startActivityForResult(new Intent(this, ConfigurationWizard.class), SWITCH_PROVIDER); + startActivityForResult(new Intent(this, ConfigurationWizard.class), REQUEST_CODE_SWITCH_PROVIDER); } private void clearDataOfLastProvider() { @@ -422,7 +428,7 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec if (entry.getKey().startsWith(Provider.KEY + ".") || entry.getKey().startsWith(Provider.CA_CERT + ".") || entry.getKey().startsWith(Provider.CA_CERT_FINGERPRINT + "." )|| - entry.getKey().equals(Constants.PREFERENCES_APP_VERSION) + entry.getKey().equals(PREFERENCES_APP_VERSION) ) { continue; } @@ -435,46 +441,53 @@ public class Dashboard extends Activity implements ProviderAPIResultReceiver.Rec } preferenceEditor.apply(); + switching_provider = false; + startActivityForResult(new Intent(this, ConfigurationWizard.class), REQUEST_CODE_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); - user_status_fragment.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(); - 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 static class DashboardReceiver implements ProviderAPIResultReceiver.Receiver{ + + private Dashboard dashboard; + + DashboardReceiver(Dashboard dashboard) { + this.dashboard = dashboard; } - } - public static Context getContext() { - return dashboardContext; + @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); + dashboard.user_status_fragment.logIn(username, password); + } else if (resultCode == ProviderAPI.FAILED_SIGNUP) { + MainActivity.sessionDialog(resultData); + } else if (resultCode == ProviderAPI.SUCCESSFUL_LOGIN) { + Dashboard.downloadVpnCertificate(); + } else if (resultCode == ProviderAPI.FAILED_LOGIN) { + MainActivity.sessionDialog(resultData); + } else if (resultCode == ProviderAPI.SUCCESSFUL_LOGOUT) { + if (switching_provider) dashboard.switchProvider(); + } else if (resultCode == ProviderAPI.LOGOUT_FAILED) { + dashboard.setResult(RESULT_CANCELED); + } else if (resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE) { + dashboard.eip_fragment.updateEipService(); + dashboard.setResult(RESULT_OK); + } else if (resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE) { + dashboard.setResult(RESULT_CANCELED); + } else if (resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE) { + dashboard.eip_fragment.updateEipService(); + dashboard.setResult(RESULT_OK); + } else if (resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE) { + dashboard.setResult(RESULT_CANCELED); + } + } } public static Provider getProvider() { return provider; } @Override public void startActivityForResult(Intent intent, int requestCode) { - intent.putExtra(Dashboard.REQUEST_CODE, requestCode); + intent.putExtra(REQUEST_CODE_KEY, requestCode); super.startActivityForResult(intent, requestCode); } diff --git a/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java b/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java index 9d3f4b52..527ce1a7 100644 --- a/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/DownloadFailedDialog.java @@ -19,7 +19,7 @@ package se.leap.bitmaskclient; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; -import android.app.DialogFragment; +import android.support.v4.app.DialogFragment; import android.content.DialogInterface; import android.os.Bundle; diff --git a/app/src/main/java/se/leap/bitmaskclient/FragmentManagerEnhanced.java b/app/src/main/java/se/leap/bitmaskclient/FragmentManagerEnhanced.java index 8ba7fa34..9fe4fd23 100644 --- a/app/src/main/java/se/leap/bitmaskclient/FragmentManagerEnhanced.java +++ b/app/src/main/java/se/leap/bitmaskclient/FragmentManagerEnhanced.java @@ -16,21 +16,23 @@ */ package se.leap.bitmaskclient; -import android.app.*; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; public class FragmentManagerEnhanced { - private FragmentManager generic_fragment_manager; + private FragmentManager genericFragmentManager; - public FragmentManagerEnhanced(FragmentManager generic_fragment_manager) { - this.generic_fragment_manager = generic_fragment_manager; + public FragmentManagerEnhanced(FragmentManager genericFragmentManager) { + this.genericFragmentManager = genericFragmentManager; } public FragmentTransaction removePreviousFragment(String tag) { - FragmentTransaction transaction = generic_fragment_manager.beginTransaction(); - Fragment previous_fragment = generic_fragment_manager.findFragmentByTag(tag); - if (previous_fragment != null) { - transaction.remove(previous_fragment); + FragmentTransaction transaction = genericFragmentManager.beginTransaction(); + Fragment previousFragment = genericFragmentManager.findFragmentByTag(tag); + if (previousFragment != null) { + transaction.remove(previousFragment); } transaction.addToBackStack(null); @@ -38,16 +40,16 @@ public class FragmentManagerEnhanced { } public void replace(int containerViewId, Fragment fragment, String tag) { - FragmentTransaction transaction = generic_fragment_manager.beginTransaction(); + FragmentTransaction transaction = genericFragmentManager.beginTransaction(); transaction.replace(containerViewId, fragment, tag).commit(); } public FragmentTransaction beginTransaction() { - return generic_fragment_manager.beginTransaction(); + return genericFragmentManager.beginTransaction(); } public Fragment findFragmentByTag(String tag) { - return generic_fragment_manager.findFragmentByTag(tag); + return genericFragmentManager.findFragmentByTag(tag); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/LoginActivity.java b/app/src/main/java/se/leap/bitmaskclient/LoginActivity.java new file mode 100644 index 00000000..a5cbf5f5 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/LoginActivity.java @@ -0,0 +1,35 @@ +package se.leap.bitmaskclient; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.util.Log; + +import butterknife.OnClick; + +/** + * Created by fupduck on 09.01.18. + */ + +public class LoginActivity extends ProviderCredentialsBaseActivity { + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.a_login); + + setProviderHeaderText("providerNAME"); + setProviderHeaderLogo(R.drawable.mask); + } + + @Override + protected void onResume() { + super.onResume(); + } + + @Override + @OnClick(R.id.button) + void handleButton() { + login(getUsername(), getPassword()); + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/MainActivity.java b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java new file mode 100644 index 00000000..41e496bb --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/MainActivity.java @@ -0,0 +1,51 @@ +package se.leap.bitmaskclient; + + +import android.os.Bundle; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; + +import se.leap.bitmaskclient.drawer.NavigationDrawerFragment; +import se.leap.bitmaskclient.userstatus.SessionDialog; + + +public class MainActivity extends AppCompatActivity { + + private static Provider provider = new Provider(); + private static FragmentManagerEnhanced fragmentManager; + + + /** + * Fragment managing the behaviors, interactions and presentation of the navigation drawer. + */ + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); + + NavigationDrawerFragment navigationDrawerFragment = (NavigationDrawerFragment) + getSupportFragmentManager().findFragmentById(R.id.navigation_drawer); + + fragmentManager = new FragmentManagerEnhanced(getSupportFragmentManager()); + // Set up the drawer. + navigationDrawerFragment.setUp( + R.id.navigation_drawer, + (DrawerLayout) findViewById(R.id.drawer_layout)); + + } + + public static void sessionDialog(Bundle resultData) { + try { + FragmentTransaction transaction = fragmentManager.removePreviousFragment(SessionDialog.TAG); + SessionDialog.getInstance(provider, resultData).show(transaction, SessionDialog.TAG); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java b/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java index f9aa2660..2a8aa42f 100644 --- a/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java +++ b/app/src/main/java/se/leap/bitmaskclient/OnBootReceiver.java @@ -7,9 +7,11 @@ import android.content.SharedPreferences; import android.util.Log; import static android.content.Intent.ACTION_BOOT_COMPLETED; +import static se.leap.bitmaskclient.Constants.APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE; import static se.leap.bitmaskclient.Constants.EIP_IS_ALWAYS_ON; import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; public class OnBootReceiver extends BroadcastReceiver { @@ -22,7 +24,7 @@ public class OnBootReceiver extends BroadcastReceiver { if (intent == null || !ACTION_BOOT_COMPLETED.equals(intent.getAction())) { return; } - preferences = context.getSharedPreferences(Constants.SHARED_PREFERENCES, Context.MODE_PRIVATE); + preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); boolean providerConfigured = !preferences.getString(PROVIDER_VPN_CERTIFICATE, "").isEmpty(); boolean startOnBoot = preferences.getBoolean(EIP_RESTART_ON_BOOT, false); boolean isAlwaysOnConfigured = preferences.getBoolean(EIP_IS_ALWAYS_ON, false); @@ -33,17 +35,17 @@ public class OnBootReceiver extends BroadcastReceiver { return; } if (startOnBoot) { - Intent dashboard_intent = new Intent(context, Dashboard.class); - dashboard_intent.putExtra(EIP_RESTART_ON_BOOT, true); - dashboard_intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(dashboard_intent); + Intent dashboardIntent = new Intent(context, Dashboard.class); + dashboardIntent.putExtra(EIP_RESTART_ON_BOOT, true); + dashboardIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(dashboardIntent); } } else { if (isAlwaysOnConfigured) { - Intent dashboard_intent = new Intent(context, Dashboard.class); - dashboard_intent.putExtra(Dashboard.ACTION_CONFIGURE_ALWAYS_ON_PROFILE, true); - dashboard_intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(dashboard_intent); + Intent dashboardIntent = new Intent(context, Dashboard.class); + dashboardIntent.putExtra(APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE, true); + dashboardIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(dashboardIntent); } } } diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java b/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java index 5a6aabc0..ccc71a67 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderAPI.java @@ -21,7 +21,7 @@ import android.app.IntentService; import android.content.Intent; import android.content.SharedPreferences; -import de.blinkt.openvpn.core.Preferences; +import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; /** * Implements HTTP api methods (encapsulated in {{@link ProviderApiManager}}) @@ -47,12 +47,15 @@ public class ProviderAPI extends IntentService implements ProviderApiManagerBase DOWNLOAD_CERTIFICATE = "downloadUserAuthedCertificate", PARAMETERS = "parameters", RESULT_KEY = "result", + RESULT_CODE = "RESULT CODE", RECEIVER_KEY = "receiver", ERRORS = "errors", ERRORID = "errorId", UPDATE_PROGRESSBAR = "update_progressbar", CURRENT_PROGRESS = "current_progress", - DOWNLOAD_EIP_SERVICE = TAG + ".DOWNLOAD_EIP_SERVICE"; + DOWNLOAD_EIP_SERVICE = TAG + ".DOWNLOAD_EIP_SERVICE", + PROVIDER_SET_UP = TAG + ".PROVIDER_SET_UP", + PROVIDER_API_EVENT = "PROVIDER_API_EVENT"; final public static int SUCCESSFUL_LOGIN = 3, @@ -106,7 +109,7 @@ public class ProviderAPI extends IntentService implements ProviderApiManagerBase } @Override - public void broadcastProgress(Intent intent) { + public void broadcastEvent(Intent intent) { sendBroadcast(intent); } @@ -117,7 +120,7 @@ public class ProviderAPI extends IntentService implements ProviderApiManagerBase private ProviderApiManager initApiManager() { - SharedPreferences preferences = getSharedPreferences(Constants.SHARED_PREFERENCES, MODE_PRIVATE); + SharedPreferences preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); OkHttpClientGenerator clientGenerator = new OkHttpClientGenerator(preferences, getResources()); return new ProviderApiManager(preferences, getResources(), clientGenerator, this); } diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java index 464898a5..da2e4c8b 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderApiManagerBase.java @@ -54,6 +54,10 @@ import se.leap.bitmaskclient.userstatus.User; import se.leap.bitmaskclient.userstatus.UserStatus; import static se.leap.bitmaskclient.ConfigHelper.getFingerprintFromCertificate; +import static se.leap.bitmaskclient.Constants.PROVIDER_ALLOWED_REGISTERED; +import static se.leap.bitmaskclient.Constants.PROVIDER_ALLOW_ANONYMOUS; +import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY; +import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.DownloadFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING; import static se.leap.bitmaskclient.DownloadFailedDialog.DOWNLOAD_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON; import static se.leap.bitmaskclient.DownloadFailedDialog.DOWNLOAD_ERRORS.ERROR_INVALID_CERTIFICATE; @@ -69,6 +73,8 @@ import static se.leap.bitmaskclient.ProviderAPI.FAILED_LOGIN; import static se.leap.bitmaskclient.ProviderAPI.FAILED_SIGNUP; import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE; import static se.leap.bitmaskclient.ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE; +import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_API_EVENT; +import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_SET_UP; import static se.leap.bitmaskclient.ProviderAPI.LOGOUT_FAILED; import static se.leap.bitmaskclient.ProviderAPI.LOG_IN; import static se.leap.bitmaskclient.ProviderAPI.LOG_OUT; @@ -76,6 +82,7 @@ import static se.leap.bitmaskclient.ProviderAPI.PARAMETERS; import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_NOK; import static se.leap.bitmaskclient.ProviderAPI.PROVIDER_OK; import static se.leap.bitmaskclient.ProviderAPI.RECEIVER_KEY; +import static se.leap.bitmaskclient.ProviderAPI.RESULT_CODE; import static se.leap.bitmaskclient.ProviderAPI.RESULT_KEY; import static se.leap.bitmaskclient.ProviderAPI.SET_UP_PROVIDER; import static se.leap.bitmaskclient.ProviderAPI.SIGN_UP; @@ -89,6 +96,7 @@ import static se.leap.bitmaskclient.R.string.error_io_exception_user_message; import static se.leap.bitmaskclient.R.string.error_json_exception_user_message; import static se.leap.bitmaskclient.R.string.error_no_such_algorithm_exception_user_message; import static se.leap.bitmaskclient.R.string.malformed_url; +import static se.leap.bitmaskclient.R.string.routes_info_excl; import static se.leap.bitmaskclient.R.string.server_unreachable_message; import static se.leap.bitmaskclient.R.string.service_is_down_error; import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_cert; @@ -103,7 +111,7 @@ import static se.leap.bitmaskclient.R.string.warning_expired_provider_cert; public abstract class ProviderApiManagerBase { public interface ProviderApiServiceCallback { - void broadcastProgress(Intent intent); + void broadcastEvent(Intent intent); } private ProviderApiServiceCallback serviceCallback; @@ -167,50 +175,56 @@ public abstract class ProviderApiManagerBase { Bundle result = setUpProvider(parameters); if (go_ahead) { if (result.getBoolean(RESULT_KEY)) { - receiver.send(PROVIDER_OK, result); + sendToReceiverOrBroadcast(receiver, PROVIDER_OK, result); } else { - receiver.send(PROVIDER_NOK, result); + sendToReceiverOrBroadcast(receiver, PROVIDER_NOK, result); } } } else if (action.equalsIgnoreCase(SIGN_UP)) { UserStatus.updateStatus(UserStatus.SessionStatus.SIGNING_UP, resources); Bundle result = tryToRegister(parameters); if (result.getBoolean(RESULT_KEY)) { - receiver.send(SUCCESSFUL_SIGNUP, result); + sendToReceiverOrBroadcast(receiver, SUCCESSFUL_SIGNUP, result); } else { - receiver.send(FAILED_SIGNUP, result); + sendToReceiverOrBroadcast(receiver, FAILED_SIGNUP, result); } } else if (action.equalsIgnoreCase(LOG_IN)) { UserStatus.updateStatus(UserStatus.SessionStatus.LOGGING_IN, resources); Bundle result = tryToAuthenticate(parameters); if (result.getBoolean(RESULT_KEY)) { - receiver.send(SUCCESSFUL_LOGIN, result); + sendToReceiverOrBroadcast(receiver, SUCCESSFUL_LOGIN, result); UserStatus.updateStatus(UserStatus.SessionStatus.LOGGED_IN, resources); } else { - receiver.send(FAILED_LOGIN, result); + sendToReceiverOrBroadcast(receiver, FAILED_LOGIN, result); UserStatus.updateStatus(UserStatus.SessionStatus.NOT_LOGGED_IN, resources); } } else if (action.equalsIgnoreCase(LOG_OUT)) { UserStatus.updateStatus(UserStatus.SessionStatus.LOGGING_OUT, resources); if (logOut()) { - receiver.send(SUCCESSFUL_LOGOUT, Bundle.EMPTY); + sendToReceiverOrBroadcast(receiver, SUCCESSFUL_LOGOUT, Bundle.EMPTY); UserStatus.updateStatus(UserStatus.SessionStatus.LOGGED_OUT, resources); } else { - receiver.send(LOGOUT_FAILED, Bundle.EMPTY); + sendToReceiverOrBroadcast(receiver, LOGOUT_FAILED, Bundle.EMPTY); UserStatus.updateStatus(UserStatus.SessionStatus.DIDNT_LOG_OUT, resources); } } else if (action.equalsIgnoreCase(DOWNLOAD_CERTIFICATE)) { if (updateVpnCertificate()) { - receiver.send(CORRECTLY_DOWNLOADED_CERTIFICATE, Bundle.EMPTY); + sendToReceiverOrBroadcast(receiver, CORRECTLY_DOWNLOADED_CERTIFICATE, Bundle.EMPTY); } else { - receiver.send(INCORRECTLY_DOWNLOADED_CERTIFICATE, Bundle.EMPTY); + sendToReceiverOrBroadcast(receiver, INCORRECTLY_DOWNLOADED_CERTIFICATE, Bundle.EMPTY); } } else if (action.equalsIgnoreCase(DOWNLOAD_EIP_SERVICE)) { Bundle result = getAndSetEipServiceJson(); if (result.getBoolean(RESULT_KEY)) { - receiver.send(CORRECTLY_DOWNLOADED_EIP_SERVICE, result); + sendToReceiverOrBroadcast(receiver, CORRECTLY_DOWNLOADED_EIP_SERVICE, result); } else { - receiver.send(INCORRECTLY_DOWNLOADED_EIP_SERVICE, result); + sendToReceiverOrBroadcast(receiver, INCORRECTLY_DOWNLOADED_EIP_SERVICE, result); + } + } else if (action.equalsIgnoreCase(PROVIDER_SET_UP)) { + if(EIP_SERVICE_JSON_DOWNLOADED && CA_CERT_DOWNLOADED && PROVIDER_JSON_DOWNLOADED ) { + if(receiver!= null) { + receiver.send(PROVIDER_OK, Bundle.EMPTY); + } } } } @@ -417,21 +431,36 @@ public abstract class ProviderApiManagerBase { return userNotificationBundle; } + void sendToReceiverOrBroadcast(ResultReceiver receiver, int resultCode, Bundle resultData) { + if (receiver != null) { + receiver.send(resultCode, resultData); + } else { + broadcastEvent(PROVIDER_API_EVENT, resultCode, resultData); + } + } + /** * Sets up an intent with the progress value passed as a parameter * and sends it as a broadcast. * * @param progress */ - protected void broadcastProgress(int progress) { - Intent intentUpdate = new Intent(); - intentUpdate.setAction(UPDATE_PROGRESSBAR); + void broadcastProgress(int progress) { + Intent intentUpdate = new Intent(UPDATE_PROGRESSBAR); intentUpdate.addCategory(Intent.CATEGORY_DEFAULT); intentUpdate.putExtra(CURRENT_PROGRESS, progress); - serviceCallback.broadcastProgress(intentUpdate); - //sendBroadcast(intentUpdate); + serviceCallback.broadcastEvent(intentUpdate); } + void broadcastEvent(String action, int resultCode , Bundle resultData) { + Intent intentUpdate = new Intent(action); + intentUpdate.addCategory(Intent.CATEGORY_DEFAULT); + intentUpdate.putExtra(RESULT_CODE, resultCode); + intentUpdate.putExtra(RESULT_KEY, resultData); + serviceCallback.broadcastEvent(intentUpdate); + } + + /** * Validates parameters entered by the user to log in * @@ -674,8 +703,8 @@ public abstract class ProviderApiManagerBase { //valid certificate: skip download, save loaded provider CA cert and provider definition directly try { preferences.edit().putString(Provider.KEY, providerDefinition.toString()). - putBoolean(Constants.PROVIDER_ALLOW_ANONYMOUS, providerDefinition.getJSONObject(Provider.SERVICE).getBoolean(Constants.PROVIDER_ALLOW_ANONYMOUS)). - putBoolean(Constants.PROVIDER_ALLOWED_REGISTERED, providerDefinition.getJSONObject(Provider.SERVICE).getBoolean(Constants.PROVIDER_ALLOWED_REGISTERED)). + putBoolean(PROVIDER_ALLOW_ANONYMOUS, providerDefinition.getJSONObject(Provider.SERVICE).getBoolean(PROVIDER_ALLOW_ANONYMOUS)). + putBoolean(PROVIDER_ALLOWED_REGISTERED, providerDefinition.getJSONObject(Provider.SERVICE).getBoolean(PROVIDER_ALLOWED_REGISTERED)). putString(Provider.CA_CERT, providerCaCert).commit(); CA_CERT_DOWNLOADED = true; PROVIDER_JSON_DOWNLOADED = true; @@ -912,11 +941,11 @@ public abstract class ProviderApiManagerBase { RSAPrivateKey key = ConfigHelper.parseRsaKeyFromString(keyString); keyString = Base64.encodeToString(key.getEncoded(), Base64.DEFAULT); - preferences.edit().putString(Constants.PROVIDER_PRIVATE_KEY, "-----BEGIN RSA PRIVATE KEY-----\n" + keyString + "-----END RSA PRIVATE KEY-----").commit(); + preferences.edit().putString(PROVIDER_PRIVATE_KEY, "-----BEGIN RSA PRIVATE KEY-----\n" + keyString + "-----END RSA PRIVATE KEY-----").commit(); X509Certificate certificate = ConfigHelper.parseX509CertificateFromString(certificateString); certificateString = Base64.encodeToString(certificate.getEncoded(), Base64.DEFAULT); - preferences.edit().putString(Constants.PROVIDER_VPN_CERTIFICATE, "-----BEGIN CERTIFICATE-----\n" + certificateString + "-----END CERTIFICATE-----").commit(); + preferences.edit().putString(PROVIDER_VPN_CERTIFICATE, "-----BEGIN CERTIFICATE-----\n" + certificateString + "-----END CERTIFICATE-----").commit(); return true; } catch (CertificateException e) { // TODO Auto-generated catch block diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java b/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java new file mode 100644 index 00000000..c61b078f --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderCredentialsBaseActivity.java @@ -0,0 +1,134 @@ +package se.leap.bitmaskclient; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.support.annotation.DrawableRes; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.design.widget.TextInputEditText; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +import butterknife.InjectView; +import butterknife.OnClick; +import se.leap.bitmaskclient.userstatus.SessionDialog; +import se.leap.bitmaskclient.userstatus.User; + +/** + * Created by fupduck on 09.01.18. + */ + +public abstract class ProviderCredentialsBaseActivity extends ButterKnifeActivity { + + protected ProviderAPIResultReceiver providerAPIResultReceiver; + + @InjectView(R.id.provider_header_logo) + ImageView providerHeaderLogo; + + @InjectView(R.id.provider_header_text) + TextView providerHeaderText; + + @InjectView(R.id.provider_credentials_username) + TextInputEditText providerCredentialsUsername; + + @InjectView(R.id.provider_credentials_password) + TextInputEditText providerCredentialsPassword; + + @InjectView(R.id.button) + Button providerCredentialsButton; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + providerAPIResultReceiver = new ProviderAPIResultReceiver(new Handler(), new ProviderCredentialsReceiver(this)); + + } + + @OnClick(R.id.button) + abstract void handleButton(); + + protected void setProviderHeaderLogo(@DrawableRes int providerHeaderLogo) { + this.providerHeaderLogo.setImageResource(providerHeaderLogo); + } + + protected void setProviderHeaderText(String providerHeaderText) { + this.providerHeaderText.setText(providerHeaderText); + } + + protected void setProviderHeaderText(@StringRes int providerHeaderText) { + this.providerHeaderText.setText(providerHeaderText); + } + + protected void setButtonText(@StringRes int buttonText) { + providerCredentialsButton.setText(buttonText); + } + + String getUsername() { + return providerCredentialsUsername.getText().toString(); + } + + String getPassword() { + return providerCredentialsPassword.getText().toString(); + } + + void login(String username, String password) { + User.setUserName(username); + Bundle parameters = bundlePassword(password); + ProviderAPICommand.execute(parameters, ProviderAPI.LOG_IN, providerAPIResultReceiver); + } + + public void signUp(String username, String password) { + User.setUserName(username); + Bundle parameters = bundlePassword(password); + ProviderAPICommand.execute(parameters, ProviderAPI.SIGN_UP, providerAPIResultReceiver); + } + protected Bundle bundlePassword(String password) { + Bundle parameters = new Bundle(); + if (!password.isEmpty()) + parameters.putString(SessionDialog.PASSWORD, password); + return parameters; + } + + public static class ProviderCredentialsReceiver implements ProviderAPIResultReceiver.Receiver{ + + private ProviderCredentialsBaseActivity activity; + + ProviderCredentialsReceiver(ProviderCredentialsBaseActivity activity) { + this.activity = activity; + } + + @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); + activity.login(username, password); + } else if (resultCode == ProviderAPI.FAILED_SIGNUP) { + //MainActivity.sessionDialog(resultData); + } else if (resultCode == ProviderAPI.SUCCESSFUL_LOGIN) { + Intent intent = new Intent(activity, MainActivity.class); + activity.startActivity(intent); + } else if (resultCode == ProviderAPI.FAILED_LOGIN) { + //MainActivity.sessionDialog(resultData); +// TODO MOVE +// } else if (resultCode == ProviderAPI.SUCCESSFUL_LOGOUT) { +// if (switching_provider) activity.switchProvider(); +// } else if (resultCode == ProviderAPI.LOGOUT_FAILED) { +// activity.setResult(RESULT_CANCELED); +// } else if (resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_CERTIFICATE) { +// activity.eip_fragment.updateEipService(); +// activity.setResult(RESULT_OK); +// } else if (resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_CERTIFICATE) { +// activity.setResult(RESULT_CANCELED); +// } else if (resultCode == ProviderAPI.CORRECTLY_DOWNLOADED_EIP_SERVICE) { +// activity.eip_fragment.updateEipService(); +// activity.setResult(RESULT_OK); +// } else if (resultCode == ProviderAPI.INCORRECTLY_DOWNLOADED_EIP_SERVICE) { +// activity.setResult(RESULT_CANCELED); + } + } + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java index 92d5da9f..62c04c1a 100644 --- a/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/ProviderManager.java @@ -27,39 +27,39 @@ public class ProviderManager implements AdapteeCollection<Provider> { private static final String TAG = ProviderManager.class.getName(); private AssetManager assets_manager; - private File external_files_dir; - private Set<Provider> default_providers; - private Set<Provider> custom_providers; + private File externalFilesDir; + private Set<Provider> defaultProviders; + private Set<Provider> customProviders; private static ProviderManager instance; final protected static String URLS = "urls"; - public static ProviderManager getInstance(AssetManager assets_manager, File external_files_dir) { + public static ProviderManager getInstance(AssetManager assetsManager, File externalFilesDir) { if (instance == null) - instance = new ProviderManager(assets_manager, external_files_dir); + instance = new ProviderManager(assetsManager, externalFilesDir); return instance; } - public ProviderManager(AssetManager assets_manager, File external_files_dir) { - this.assets_manager = assets_manager; - addDefaultProviders(assets_manager); - addCustomProviders(external_files_dir); + public ProviderManager(AssetManager assetManager, File externalFilesDir) { + this.assets_manager = assetManager; + addDefaultProviders(assetManager); + addCustomProviders(externalFilesDir); } private void addDefaultProviders(AssetManager assets_manager) { try { - default_providers = providersFromAssets(URLS, assets_manager.list(URLS)); + defaultProviders = providersFromAssets(URLS, assets_manager.list(URLS)); } catch (IOException e) { e.printStackTrace(); } } - private Set<Provider> providersFromAssets(String directory, String[] relative_file_paths) { + private Set<Provider> providersFromAssets(String directory, String[] relativeFilePaths) { Set<Provider> providers = new HashSet<Provider>(); - for (String file : relative_file_paths) { + for (String file : relativeFilePaths) { String mainUrl = null; String certificate = null; String providerDefinition = null; @@ -83,10 +83,10 @@ public class ProviderManager implements AdapteeCollection<Provider> { } - private void addCustomProviders(File external_files_dir) { - this.external_files_dir = external_files_dir; - custom_providers = external_files_dir != null && external_files_dir.isDirectory() ? - providersFromFiles(external_files_dir.list()) : + private void addCustomProviders(File externalFilesDir) { + this.externalFilesDir = externalFilesDir; + customProviders = externalFilesDir != null && externalFilesDir.isDirectory() ? + providersFromFiles(externalFilesDir.list()) : new HashSet<Provider>(); } @@ -94,7 +94,7 @@ public class ProviderManager implements AdapteeCollection<Provider> { Set<Provider> providers = new HashSet<Provider>(); try { for (String file : files) { - String main_url = extractMainUrlFromInputStream(new FileInputStream(external_files_dir.getAbsolutePath() + "/" + file)); + String main_url = extractMainUrlFromInputStream(new FileInputStream(externalFilesDir.getAbsolutePath() + "/" + file)); providers.add(new Provider(new URL(main_url))); } } catch (MalformedURLException | FileNotFoundException e) { @@ -105,21 +105,21 @@ public class ProviderManager implements AdapteeCollection<Provider> { } private String extractMainUrlFromInputStream(InputStream input_stream) { - String main_url = ""; + String mainUrl = ""; JSONObject file_contents = inputStreamToJson(input_stream); if (file_contents != null) - main_url = file_contents.optString(Provider.MAIN_URL); - return main_url; + mainUrl = file_contents.optString(Provider.MAIN_URL); + return mainUrl; } - private JSONObject inputStreamToJson(InputStream input_stream) { + private JSONObject inputStreamToJson(InputStream inputStream) { JSONObject json = null; try { - byte[] bytes = new byte[input_stream.available()]; - if (input_stream.read(bytes) > 0) + byte[] bytes = new byte[inputStream.available()]; + if (inputStream.read(bytes) > 0) json = new JSONObject(new String(bytes)); - input_stream.reset(); + inputStream.reset(); } catch (IOException | JSONException e) { e.printStackTrace(); } @@ -128,9 +128,9 @@ public class ProviderManager implements AdapteeCollection<Provider> { public Set<Provider> providers() { Set<Provider> all_providers = new HashSet<Provider>(); - all_providers.addAll(default_providers); - if(custom_providers != null) - all_providers.addAll(custom_providers); + all_providers.addAll(defaultProviders); + if(customProviders != null) + all_providers.addAll(customProviders); return all_providers; } @@ -151,40 +151,40 @@ public class ProviderManager implements AdapteeCollection<Provider> { @Override public boolean add(Provider element) { - if (!default_providers.contains(element)) - return custom_providers.add(element); + if (!defaultProviders.contains(element)) + return customProviders.add(element); else return true; } @Override public boolean remove(Object element) { - return custom_providers.remove(element); + return customProviders.remove(element); } @Override public boolean addAll(Collection<? extends Provider> elements) { - return custom_providers.addAll(elements); + return customProviders.addAll(elements); } @Override public boolean removeAll(Collection<?> elements) { if(!elements.getClass().equals(Provider.class)) return false; - return default_providers.removeAll(elements) || custom_providers.removeAll(elements); + return defaultProviders.removeAll(elements) || customProviders.removeAll(elements); } @Override public void clear() { - default_providers.clear(); - custom_providers.clear(); + defaultProviders.clear(); + customProviders.clear(); } protected void saveCustomProvidersToFile() { try { - for (Provider provider : custom_providers) { - File provider_file = new File(external_files_dir, provider.getName() + ".json"); - if (!provider_file.exists()) { - FileWriter writer = new FileWriter(provider_file); + for (Provider provider : customProviders) { + File providerFile = new File(externalFilesDir, provider.getName() + ".json"); + if (!providerFile.exists()) { + FileWriter writer = new FileWriter(providerFile); writer.write(provider.toJson().toString()); writer.close(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/SignupActivity.java b/app/src/main/java/se/leap/bitmaskclient/SignupActivity.java new file mode 100644 index 00000000..f6344065 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/SignupActivity.java @@ -0,0 +1,66 @@ +package se.leap.bitmaskclient; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.design.widget.TextInputEditText; +import android.support.design.widget.TextInputLayout; +import android.text.Editable; +import android.text.TextWatcher; + +import butterknife.InjectView; +import butterknife.OnClick; + +/** + * Create an account with a provider + */ + +public class SignupActivity extends ProviderCredentialsBaseActivity { + + @InjectView(R.id.provider_credentials_password_verification) + TextInputEditText providerCredentialsPasswordVerification; + + @InjectView(R.id.provider_credentials_password_verification_layout) + TextInputLayout providerCredentialsPasswordVerificationLayout; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.a_signup); + + setProviderHeaderText("providerNAME"); + setProviderHeaderLogo(R.drawable.mask); + + setButtonText(R.string.signup_button); + + providerCredentialsPasswordVerification.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + if(getPassword().equals(getPasswordVerification())) { + providerCredentialsPasswordVerificationLayout.setError(null); + } else { + providerCredentialsPasswordVerificationLayout.setError(getString(R.string.password_mismatch)); + } + } + }); + } + + @Override + @OnClick(R.id.button) + void handleButton() { + if (getPassword().equals(getPasswordVerification())) { + signUp(getUsername(), getPassword()); + } + } + + private String getPasswordVerification() { + return providerCredentialsPasswordVerification.getText().toString(); + } +} diff --git a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java index ec972a75..43d7f152 100644 --- a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java @@ -15,6 +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.PREFERENCES_APP_VERSION; +import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; + /** * Activity shown at startup. Evaluates if App is started for the first time or has been upgraded * and acts and calls another activity accordingly. @@ -39,7 +42,7 @@ public class StartActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - preferences = getSharedPreferences(Constants.SHARED_PREFERENCES, MODE_PRIVATE); + preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); Log.d(TAG, "Started"); @@ -50,6 +53,7 @@ public class StartActivity extends Activity { case FIRST: storeAppVersion(); // TODO start ProfileCreation & replace below code + // (new Intent(getActivity(), ConfigurationWizard.class), Constants.REQUEST_CODE_SWITCH_PROVIDER); break; case UPGRADE: @@ -68,7 +72,7 @@ public class StartActivity extends Activity { User.init(getString(R.string.default_username)); // go to Dashboard - Intent intent = new Intent(this, Dashboard.class); + Intent intent = new Intent(this, MainActivity.class); startActivity(intent); } @@ -80,7 +84,7 @@ public class StartActivity extends Activity { private int checkAppStart() { try { versionCode = getPackageManager().getPackageInfo(getPackageName(), 0).versionCode; - previousVersionCode = preferences.getInt(Constants.PREFERENCES_APP_VERSION, -1); + previousVersionCode = preferences.getInt(PREFERENCES_APP_VERSION, -1); // versions do match -> normal start if (versionCode == previousVersionCode) { @@ -134,7 +138,7 @@ public class StartActivity extends Activity { } private void storeAppVersion() { - preferences.edit().putInt(Constants.PREFERENCES_APP_VERSION, versionCode).apply(); + preferences.edit().putInt(PREFERENCES_APP_VERSION, versionCode).apply(); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java index 0dfccc68..f1463029 100644 --- a/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/VpnFragment.java @@ -18,17 +18,18 @@ package se.leap.bitmaskclient; import android.app.Activity; import android.app.AlertDialog; -import android.app.Fragment; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; +import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; +import android.support.v4.app.Fragment; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -63,6 +64,7 @@ import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT; import static se.leap.bitmaskclient.Constants.PROVIDER_ALLOWED_REGISTERED; import static se.leap.bitmaskclient.Constants.PROVIDER_ALLOW_ANONYMOUS; import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; +import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; public class VpnFragment extends Fragment implements Observer { @@ -72,15 +74,16 @@ public class VpnFragment extends Fragment implements Observer { protected static final String IS_CONNECTED = TAG + ".is_connected"; public static final String START_EIP_ON_BOOT = "start on boot"; + private SharedPreferences preferences; + @InjectView(R.id.vpn_status_image) - FabButton vpn_status_image; + FabButton vpnStatusImage; @InjectView(R.id.vpn_main_button) - Button main_button; + Button mainButton; - private Dashboard dashboard; - private static EIPReceiver eip_receiver; - private static EipStatus eip_status; - private boolean wants_to_connect; + private static EIPReceiver eipReceiver; + private static EipStatus eipStatus; + private boolean wantsToConnect; private IOpenVPNServiceInternal mService; private ServiceConnection openVpnConnection = new ServiceConnection() { @@ -101,20 +104,18 @@ public class VpnFragment extends Fragment implements Observer { }; - //FIXME: replace with onAttach(Context context) - public void onAttach(Activity activity) { - super.onAttach(activity); - - dashboard = (Dashboard) activity; + public void onAttach(Context context) { + super.onAttach(context); downloadEIPServiceConfig(); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - eip_status = EipStatus.getInstance(); - eip_status.addObserver(this); - eip_receiver = new EIPReceiver(new Handler()); + eipStatus = EipStatus.getInstance(); + eipStatus.addObserver(this); + eipReceiver = new EIPReceiver(new Handler()); + preferences = getActivity().getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); } @Override @@ -126,7 +127,6 @@ public class VpnFragment extends Fragment implements Observer { if (arguments != null && arguments.containsKey(START_EIP_ON_BOOT) && arguments.getBoolean(START_EIP_ON_BOOT)) { startEipFromScratch(); } - return view; } @@ -142,22 +142,22 @@ public class VpnFragment extends Fragment implements Observer { @Override public void onPause() { super.onPause(); - dashboard.unbindService(openVpnConnection); + getActivity().unbindService(openVpnConnection); } @Override public void onSaveInstanceState(Bundle outState) { - outState.putBoolean(IS_CONNECTED, eip_status.isConnected()); + outState.putBoolean(IS_CONNECTED, eipStatus.isConnected()); super.onSaveInstanceState(outState); } private void saveStatus(boolean restartOnBoot) { - Dashboard.preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, restartOnBoot).apply(); + preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, restartOnBoot).apply(); } @OnClick(R.id.vpn_main_button) void handleIcon() { - if (eip_status.isConnected() || eip_status.isConnecting()) + if (eipStatus.isConnected() || eipStatus.isConnecting()) handleSwitchOff(); else handleSwitchOn(); @@ -167,9 +167,9 @@ public class VpnFragment extends Fragment implements Observer { if (canStartEIP()) startEipFromScratch(); else if (canLogInToStartEIP()) { - wants_to_connect = true; + wantsToConnect = true; Bundle bundle = new Bundle(); - dashboard.sessionDialog(bundle); + MainActivity.sessionDialog(bundle); } else { Log.d(TAG, "WHAT IS GOING ON HERE?!"); // TODO: implement a fallback: check if vpncertificate was not downloaded properly or give @@ -178,21 +178,21 @@ public class VpnFragment extends Fragment implements Observer { } private boolean canStartEIP() { - boolean certificateExists = !Dashboard.preferences.getString(PROVIDER_VPN_CERTIFICATE, "").isEmpty(); - boolean isAllowedAnon = Dashboard.preferences.getBoolean(PROVIDER_ALLOW_ANONYMOUS, false); - return (isAllowedAnon || certificateExists) && !eip_status.isConnected() && !eip_status.isConnecting(); + boolean certificateExists = !preferences.getString(PROVIDER_VPN_CERTIFICATE, "").isEmpty(); + boolean isAllowedAnon = preferences.getBoolean(PROVIDER_ALLOW_ANONYMOUS, false); + return (isAllowedAnon || certificateExists) && !eipStatus.isConnected() && !eipStatus.isConnecting(); } private boolean canLogInToStartEIP() { - boolean isAllowedRegistered = Dashboard.preferences.getBoolean(PROVIDER_ALLOWED_REGISTERED, false); + boolean isAllowedRegistered = preferences.getBoolean(PROVIDER_ALLOWED_REGISTERED, false); boolean isLoggedIn = !LeapSRPSession.getToken().isEmpty(); - return isAllowedRegistered && !isLoggedIn && !eip_status.isConnecting() && !eip_status.isConnected(); + return isAllowedRegistered && !isLoggedIn && !eipStatus.isConnecting() && !eipStatus.isConnected(); } private void handleSwitchOff() { - if (eip_status.isConnecting()) { + if (eipStatus.isConnecting()) { askPendingStartCancellation(); - } else if (eip_status.isConnected()) { + } else if (eipStatus.isConnected()) { askToStopEIP(); } else { updateIcon(); @@ -200,16 +200,17 @@ public class VpnFragment extends Fragment implements Observer { } private void askPendingStartCancellation() { - AlertDialog.Builder alertBuilder = new AlertDialog.Builder(dashboard); - alertBuilder.setTitle(dashboard.getString(R.string.eip_cancel_connect_title)) - .setMessage(dashboard.getString(R.string.eip_cancel_connect_text)) + Activity activity = getActivity(); + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity()); + alertBuilder.setTitle(activity.getString(R.string.eip_cancel_connect_title)) + .setMessage(activity.getString(R.string.eip_cancel_connect_text)) .setPositiveButton((android.R.string.yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { stopEipIfPossible(); } }) - .setNegativeButton(dashboard.getString(android.R.string.no), new DialogInterface.OnClickListener() { + .setNegativeButton(activity.getString(android.R.string.no), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } @@ -218,14 +219,14 @@ public class VpnFragment extends Fragment implements Observer { } public void startEipFromScratch() { - wants_to_connect = false; + wantsToConnect = false; saveStatus(true); eipCommand(EIP_ACTION_START); } private void stop() { saveStatus(false); - if (eip_status.isBlockingVpnEstablished()) { + if (eipStatus.isBlockingVpnEstablished()) { stopBlockingVpn(); } disconnect(); @@ -233,13 +234,14 @@ public class VpnFragment extends Fragment implements Observer { private void stopBlockingVpn() { Log.d(TAG, "stop VoidVpn!"); - Intent stopVoidVpnIntent = new Intent(dashboard, VoidVpnService.class); + Activity activity = getActivity(); + Intent stopVoidVpnIntent = new Intent(activity, VoidVpnService.class); stopVoidVpnIntent.setAction(EIP_ACTION_STOP_BLOCKING_VPN); - dashboard.startService(stopVoidVpnIntent); + activity.startService(stopVoidVpnIntent); } private void disconnect() { - ProfileManager.setConntectedVpnProfileDisconnected(dashboard); + ProfileManager.setConntectedVpnProfileDisconnected(getActivity()); if (mService != null) { try { mService.stopVPN(false); @@ -255,22 +257,23 @@ public class VpnFragment extends Fragment implements Observer { } private void downloadEIPServiceConfig() { - ProviderAPIResultReceiver provider_api_receiver = new ProviderAPIResultReceiver(new Handler(), dashboard); - if(eip_receiver != null) + ProviderAPIResultReceiver provider_api_receiver = new ProviderAPIResultReceiver(new Handler(), Dashboard.dashboardReceiver); + if(eipReceiver != null) ProviderAPICommand.execute(Bundle.EMPTY, ProviderAPI.DOWNLOAD_EIP_SERVICE, provider_api_receiver); } protected void askToStopEIP() { - AlertDialog.Builder alertBuilder = new AlertDialog.Builder(dashboard); - alertBuilder.setTitle(dashboard.getString(R.string.eip_cancel_connect_title)) - .setMessage(dashboard.getString(R.string.eip_warning_browser_inconsistency)) + Activity activity = getActivity(); + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(activity); + alertBuilder.setTitle(activity.getString(R.string.eip_cancel_connect_title)) + .setMessage(activity.getString(R.string.eip_warning_browser_inconsistency)) .setPositiveButton((android.R.string.yes), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { stopEipIfPossible(); } }) - .setNegativeButton(dashboard.getString(android.R.string.no), new DialogInterface.OnClickListener() { + .setNegativeButton(activity.getString(android.R.string.no), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { } @@ -289,23 +292,29 @@ public class VpnFragment extends Fragment implements Observer { * filter for the EIP class */ private void eipCommand(String action) { + Activity activity = getActivity(); // TODO validate "action"...how do we get the list of intent-filters for a class via Android API? - Intent vpn_intent = new Intent(dashboard.getApplicationContext(), EIP.class); + Intent vpn_intent = new Intent(activity.getApplicationContext(), EIP.class); vpn_intent.setAction(action); - vpn_intent.putExtra(EIP_RECEIVER, eip_receiver); - dashboard.startService(vpn_intent); + vpn_intent.putExtra(EIP_RECEIVER, eipReceiver); + activity.startService(vpn_intent); } @Override public void update(Observable observable, Object data) { if (observable instanceof EipStatus) { - eip_status = (EipStatus) observable; - dashboard.runOnUiThread(new Runnable() { - @Override - public void run() { - handleNewState(); - } - }); + eipStatus = (EipStatus) observable; + Activity activity = getActivity(); + if (activity != null) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + handleNewState(); + } + }); + } else { + Log.e("VpnFragment", "activity is null"); + } } } @@ -315,39 +324,40 @@ public class VpnFragment extends Fragment implements Observer { } private void updateIcon() { - if (eip_status.isBlocking()) { - vpn_status_image.showProgress(false); - vpn_status_image.setIcon(R.drawable.ic_stat_vpn_blocking, R.drawable.ic_stat_vpn_blocking); - vpn_status_image.setTag(R.drawable.ic_stat_vpn_blocking); - } else if (eip_status.isConnecting()) { - vpn_status_image.showProgress(true); - vpn_status_image.setIcon(R.drawable.ic_stat_vpn_empty_halo, R.drawable.ic_stat_vpn_empty_halo); - vpn_status_image.setTag(R.drawable.ic_stat_vpn_empty_halo); - } else if (eip_status.isConnected()){ - vpn_status_image.showProgress(false); - vpn_status_image.setIcon(R.drawable.ic_stat_vpn, R.drawable.ic_stat_vpn); - vpn_status_image.setTag(R.drawable.ic_stat_vpn); + if (eipStatus.isBlocking()) { + vpnStatusImage.showProgress(false); + vpnStatusImage.setIcon(R.drawable.ic_stat_vpn_blocking, R.drawable.ic_stat_vpn_blocking); + vpnStatusImage.setTag(R.drawable.ic_stat_vpn_blocking); + } else if (eipStatus.isConnecting()) { + vpnStatusImage.showProgress(true); + vpnStatusImage.setIcon(R.drawable.ic_stat_vpn_empty_halo, R.drawable.ic_stat_vpn_empty_halo); + vpnStatusImage.setTag(R.drawable.ic_stat_vpn_empty_halo); + } else if (eipStatus.isConnected()){ + vpnStatusImage.showProgress(false); + vpnStatusImage.setIcon(R.drawable.ic_stat_vpn, R.drawable.ic_stat_vpn); + vpnStatusImage.setTag(R.drawable.ic_stat_vpn); } else { - vpn_status_image.setIcon(R.drawable.ic_stat_vpn_offline, R.drawable.ic_stat_vpn_offline); - vpn_status_image.setTag(R.drawable.ic_stat_vpn_offline); - vpn_status_image.showProgress(false); + vpnStatusImage.setIcon(R.drawable.ic_stat_vpn_offline, R.drawable.ic_stat_vpn_offline); + vpnStatusImage.setTag(R.drawable.ic_stat_vpn_offline); + vpnStatusImage.showProgress(false); } } private void updateButton() { - if (eip_status.isConnecting()) { - main_button.setText(dashboard.getString(android.R.string.cancel)); - } else if (eip_status.isConnected() || isOpenVpnRunningWithoutNetwork()) { - main_button.setText(dashboard.getString(R.string.vpn_button_turn_off)); + Activity activity = getActivity(); + if (eipStatus.isConnecting()) { + mainButton.setText(activity.getString(android.R.string.cancel)); + } else if (eipStatus.isConnected() || isOpenVpnRunningWithoutNetwork()) { + mainButton.setText(activity.getString(R.string.vpn_button_turn_off)); } else { - main_button.setText(dashboard.getString(R.string.vpn_button_turn_on)); + mainButton.setText(activity.getString(R.string.vpn_button_turn_on)); } } private boolean isOpenVpnRunningWithoutNetwork() { boolean isRunning = false; try { - isRunning = eip_status.getLevel() == LEVEL_NONETWORK && + isRunning = eipStatus.getLevel() == LEVEL_NONETWORK && mService.isVpnRunning(); } catch (Exception e) { //eat me @@ -358,14 +368,15 @@ public class VpnFragment extends Fragment implements Observer { } private void bindOpenVpnService() { - Intent intent = new Intent(dashboard, OpenVPNService.class); + Activity activity = getActivity(); + Intent intent = new Intent(activity, OpenVPNService.class); intent.setAction(OpenVPNService.START_SERVICE); - dashboard.bindService(intent, openVpnConnection, Context.BIND_AUTO_CREATE); + activity.bindService(intent, openVpnConnection, Context.BIND_AUTO_CREATE); } protected class EIPReceiver extends ResultReceiver { - protected EIPReceiver(Handler handler) { + EIPReceiver(Handler handler) { super(handler); } @@ -375,53 +386,61 @@ public class VpnFragment extends Fragment implements Observer { String request = resultData.getString(EIP_REQUEST); - if (request.equals(EIP_ACTION_START)) { - switch (resultCode) { - case Activity.RESULT_OK: - break; - case Activity.RESULT_CANCELED: - - break; - } - } else if (request.equals(EIP_ACTION_STOP)) { - switch (resultCode) { - case Activity.RESULT_OK: - stop(); - break; - case Activity.RESULT_CANCELED: - break; - } - } else if (request.equals(EIP_NOTIFICATION)) { - switch (resultCode) { - case Activity.RESULT_OK: - break; - case Activity.RESULT_CANCELED: - break; - } - } else if (request.equals(EIP_ACTION_CHECK_CERT_VALIDITY)) { - switch (resultCode) { - case Activity.RESULT_OK: - break; - case Activity.RESULT_CANCELED: - dashboard.downloadVpnCertificate(); - break; - } - } else if (request.equals(EIP_ACTION_UPDATE)) { - switch (resultCode) { - case Activity.RESULT_OK: - if (wants_to_connect) - startEipFromScratch(); - break; - case Activity.RESULT_CANCELED: - handleNewState(); - break; - } + if (request == null) { + return; + } + + switch (request) { + case EIP_ACTION_START: + switch (resultCode) { + case Activity.RESULT_OK: + break; + case Activity.RESULT_CANCELED: + break; + } + break; + case EIP_ACTION_STOP: + switch (resultCode) { + case Activity.RESULT_OK: + stop(); + break; + case Activity.RESULT_CANCELED: + break; + } + break; + case EIP_NOTIFICATION: + switch (resultCode) { + case Activity.RESULT_OK: + break; + case Activity.RESULT_CANCELED: + break; + } + break; + case EIP_ACTION_CHECK_CERT_VALIDITY: + switch (resultCode) { + case Activity.RESULT_OK: + break; + case Activity.RESULT_CANCELED: + Dashboard.downloadVpnCertificate(); + break; + } + break; + case EIP_ACTION_UPDATE: + switch (resultCode) { + case Activity.RESULT_OK: + if (wantsToConnect) + startEipFromScratch(); + break; + case Activity.RESULT_CANCELED: + handleNewState(); + break; + } } } } public static EIPReceiver getReceiver() { - return eip_receiver; + return eipReceiver; } } diff --git a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java new file mode 100644 index 00000000..566134dd --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java @@ -0,0 +1,357 @@ +package se.leap.bitmaskclient.drawer; + + +import android.content.Context; +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; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBar; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.Toast; + +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 se.leap.bitmaskclient.Constants.REQUEST_CODE_SWITCH_PROVIDER; +import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; + +/** + * Fragment used for managing interactions for and presentation of a navigation drawer. + * See the <a href="https://developer.android.com/design/patterns/navigation-drawer.html#Interaction"> + * design guidelines</a> for a complete explanation of the behaviors implemented here. + */ +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. + */ + private static final String PREF_USER_LEARNED_DRAWER = "navigation_drawer_learned"; + + /** + * Helper component that ties the action bar to the navigation drawer. + */ + private ActionBarDrawerToggle mDrawerToggle; + + private DrawerLayout mDrawerLayout; + private View mDrawerView; + private ListView mDrawerSettingsListView; + private ListView mDrawerAccountsListView; + private View mFragmentContainerView; + + private int mCurrentSelectedPosition = 0; + private boolean mFromSavedInstanceState; + private boolean mUserLearnedDrawer; + + private String mTitle; + + private SharedPreferences preferences; + + public NavigationDrawerFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // 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); + } + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + // Indicate that this fragment would like to influence the set of actions in the action bar. + setHasOptionsMenu(true); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + mDrawerView = inflater.inflate(R.layout.drawer_main, container, false); + return mDrawerView; + } + + public boolean isDrawerOpen() { + return mDrawerLayout != null && mDrawerLayout.isDrawerOpen(mFragmentContainerView); + } + + /** + * Users of this fragment must call this method to set up the navigation drawer interactions. + * + * @param fragmentId The android:id of this fragment in its activity's layout. + * @param drawerLayout The DrawerLayout containing this fragment's UI. + */ + public void setUp(int fragmentId, DrawerLayout drawerLayout) { + AppCompatActivity activity = (AppCompatActivity) getActivity(); + ActionBar actionBar = activity.getSupportActionBar(); + + mDrawerSettingsListView = mDrawerView.findViewById(R.id.settingsList); + mDrawerSettingsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + selectItem(parent, position); + } + }); + + mDrawerSettingsListView.setAdapter(new ArrayAdapter<String>( + actionBar.getThemedContext(), + 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() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + selectItem(parent, position); + } + }); + + mDrawerAccountsListView.setAdapter(new ArrayAdapter<String>( + actionBar.getThemedContext(), + android.R.layout.simple_list_item_activated_1, + android.R.id.text1, + new String[]{ + User.userName(), + })); + mDrawerAccountsListView.setItemChecked(mCurrentSelectedPosition, true); + + mFragmentContainerView = activity.findViewById(fragmentId); + mDrawerLayout = drawerLayout; + + // set a custom shadow that overlays the main content when the drawer opens + mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); + + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setHomeButtonEnabled(true); + // ActionBarDrawerToggle ties together the the proper interactions + // between the navigation drawer and the action bar app icon. + mDrawerToggle = new ActionBarDrawerToggle( + getActivity(), + mDrawerLayout, + (Toolbar) drawerLayout.findViewById(R.id.toolbar), + R.string.navigation_drawer_open, + R.string.navigation_drawer_close + ) { + @Override + public void onDrawerClosed(View drawerView) { + super.onDrawerClosed(drawerView); + if (!isAdded()) { + return; + } + + getActivity().invalidateOptionsMenu(); // calls onPrepareOptionsMenu() + } + + @Override + public void onDrawerOpened(View drawerView) { + super.onDrawerOpened(drawerView); + if (!isAdded()) { + return; + } + + if (!mUserLearnedDrawer) { + // 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(); + } + + getActivity().invalidateOptionsMenu(); // calls onPrepareOptionsMenu() + } + }; + + // If the user hasn't 'learned' about the drawer, open it to introduce them to the drawer, + // per the navigation drawer design guidelines. + if (!mUserLearnedDrawer && !mFromSavedInstanceState) { + mDrawerLayout.openDrawer(mFragmentContainerView); + } + + // Defer code dependent on restoration of previous instance state. + mDrawerLayout.post(new Runnable() { + @Override + public void run() { + mDrawerToggle.syncState(); + } + }); + mDrawerLayout.addDrawerListener(mDrawerToggle); + + selectItem(mDrawerSettingsListView, 0); + } + + private void selectItem(AdapterView<?> list, int position) { + mCurrentSelectedPosition = position; + if (list != null) { + ((ListView) list).setItemChecked(position, true); + } + if (mDrawerLayout != null) { + mDrawerLayout.closeDrawer(mFragmentContainerView); + } + onNavigationDrawerItemSelected(list, position); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + } + + @Override + public void onDetach() { + super.onDetach(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt(STATE_SELECTED_POSITION, mCurrentSelectedPosition); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + // Forward the new configuration the drawer toggle component. + mDrawerToggle.onConfigurationChanged(newConfig); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + if (mDrawerLayout != null && isDrawerOpen()) { + showGlobalContextActionBar(); + } + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (mDrawerToggle.onOptionsItemSelected(item)) { + return true; + } + + if (item.getItemId() == R.id.action_example) { + Toast.makeText(getActivity(), "Example action.", Toast.LENGTH_SHORT).show(); + return true; + } + + return super.onOptionsItemSelected(item); + } + + /** + * 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. + */ + private void showGlobalContextActionBar() { + ActionBar actionBar = getActionBar(); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setTitle(R.string.app_name); + } + + private ActionBar getActionBar() { + return ((AppCompatActivity) getActivity()).getSupportActionBar(); + } + + public void onNavigationDrawerItemSelected(AdapterView<?> parent, int position) { + // update the main content by replacing fragments + FragmentManager fragmentManager = getFragmentManager(); + 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); + } else { + Log.d("Drawer", String.format("Selected position %d", position)); + switch (position) { + case 1: + // 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: + mTitle = getString(R.string.log_fragment_title); + fragment = new LogFragment(); + break; + case 3: + mTitle = getString(R.string.about_fragment_title); + fragment = new AboutFragment(); + break; + default: + mTitle = getString(R.string.vpn_fragment_title); + fragment = new VpnFragment(); + break; + } + } + + if (fragment != null) { + fragmentManager.beginTransaction() + .replace(R.id.container, fragment) + .commit(); + } + + restoreActionBar(); + } + + public void restoreActionBar() { + ActionBar actionBar = getActionBar(); + if (actionBar != null) { + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setSubtitle(mTitle); + } + } + + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java index a84ab941..a2ac9d66 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/EIP.java @@ -16,15 +16,21 @@ */ package se.leap.bitmaskclient.eip; -import android.app.*; -import android.content.*; -import android.os.*; +import android.app.Activity; +import android.app.IntentService; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.ResultReceiver; import android.util.Log; -import org.json.*; +import org.json.JSONException; +import org.json.JSONObject; -import de.blinkt.openvpn.*; -import se.leap.bitmaskclient.*; +import de.blinkt.openvpn.LaunchVPN; +import se.leap.bitmaskclient.OnBootReceiver; +import se.leap.bitmaskclient.VpnFragment; import static se.leap.bitmaskclient.Constants.EIP_ACTION_CHECK_CERT_VALIDITY; import static se.leap.bitmaskclient.Constants.EIP_ACTION_IS_RUNNING; @@ -57,8 +63,8 @@ public final class EIP extends IntentService { private static ResultReceiver mReceiver; private static SharedPreferences preferences; - private static JSONObject eip_definition; - private static GatewaysManager gateways_manager = new GatewaysManager(); + private static JSONObject eipDefinition; + private static GatewaysManager gatewaysManager = new GatewaysManager(); private static Gateway gateway; public EIP() { @@ -70,8 +76,8 @@ public final class EIP extends IntentService { super.onCreate(); context = getApplicationContext(); preferences = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); - eip_definition = eipDefinitionFromPreferences(); - if (gateways_manager.isEmpty()) + eipDefinition = eipDefinitionFromPreferences(); + if (gatewaysManager.isEmpty()) gatewaysFromPreferences(); } @@ -100,13 +106,13 @@ public final class EIP extends IntentService { * It also sets up early routes. */ private void startEIP() { - if (gateways_manager.isEmpty()) + if (gatewaysManager.isEmpty()) updateEIPService(); if (!EipStatus.getInstance().isBlockingVpnEstablished()) { earlyRoutes(); } - gateway = gateways_manager.select(); + gateway = gatewaysManager.select(); if (gateway != null && gateway.getProfile() != null) { mReceiver = VpnFragment.getReceiver(); launchActiveGateway(); @@ -122,10 +128,10 @@ public final class EIP extends IntentService { private void startAlwaysOnEIP() { Log.d(TAG, "startAlwaysOnEIP vpn"); - if (gateways_manager.isEmpty()) + if (gatewaysManager.isEmpty()) updateEIPService(); - gateway = gateways_manager.select(); + gateway = gatewaysManager.select(); if (gateway != null && gateway.getProfile() != null) { //mReceiver = VpnFragment.getReceiver(); @@ -141,9 +147,9 @@ public final class EIP extends IntentService { * VpnService is started properly. */ private void earlyRoutes() { - Intent void_vpn_launcher = new Intent(context, VoidVpnLauncher.class); - void_vpn_launcher.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(void_vpn_launcher); + Intent voidVpnLauncher = new Intent(context, VoidVpnLauncher.class); + voidVpnLauncher.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(voidVpnLauncher); } private void launchActiveGateway() { @@ -156,12 +162,12 @@ public final class EIP extends IntentService { } private void stopEIP() { - EipStatus eip_status = EipStatus.getInstance(); - int result_code = Activity.RESULT_CANCELED; - if (eip_status.isConnected() || eip_status.isConnecting()) - result_code = Activity.RESULT_OK; + EipStatus eipStatus = EipStatus.getInstance(); + int resultCode = Activity.RESULT_CANCELED; + if (eipStatus.isConnected() || eipStatus.isConnecting()) + resultCode = Activity.RESULT_OK; - tellToReceiver(EIP_ACTION_STOP, result_code); + tellToReceiver(EIP_ACTION_STOP, resultCode); } /** @@ -170,8 +176,8 @@ public final class EIP extends IntentService { * request if it's not connected, <code>Activity.RESULT_OK</code> otherwise. */ private void isRunning() { - EipStatus eip_status = EipStatus.getInstance(); - int resultCode = (eip_status.isConnected()) ? + EipStatus eipStatus = EipStatus.getInstance(); + int resultCode = (eipStatus.isConnected()) ? Activity.RESULT_OK : Activity.RESULT_CANCELED; tellToReceiver(EIP_ACTION_IS_RUNNING, resultCode); @@ -182,8 +188,8 @@ public final class EIP extends IntentService { * TODO Implement API call to refresh eip-service.json from the provider */ private void updateEIPService() { - eip_definition = eipDefinitionFromPreferences(); - if (eip_definition.length() > 0) + eipDefinition = eipDefinitionFromPreferences(); + if (eipDefinition.length() > 0) updateGateways(); tellToReceiver(EIP_ACTION_UPDATE, Activity.RESULT_OK); } @@ -191,9 +197,9 @@ public final class EIP extends IntentService { private JSONObject eipDefinitionFromPreferences() { JSONObject result = new JSONObject(); try { - String eip_definition_string = preferences.getString(PROVIDER_KEY, ""); - if (!eip_definition_string.isEmpty()) { - result = new JSONObject(eip_definition_string); + String eipDefinitionString = preferences.getString(PROVIDER_KEY, ""); + if (!eipDefinitionString.isEmpty()) { + result = new JSONObject(eipDefinitionString); } } catch (JSONException e) { // TODO Auto-generated catch block @@ -203,20 +209,20 @@ public final class EIP extends IntentService { } private void updateGateways() { - gateways_manager.clearGatewaysAndProfiles(); - gateways_manager.fromEipServiceJson(eip_definition); + gatewaysManager.clearGatewaysAndProfiles(); + gatewaysManager.fromEipServiceJson(eipDefinition); gatewaysToPreferences(); } private void gatewaysFromPreferences() { - String gateways_string = preferences.getString(Gateway.TAG, ""); - gateways_manager = new GatewaysManager(context, preferences); - gateways_manager.addFromString(gateways_string); + String gatewaysString = preferences.getString(Gateway.TAG, ""); + gatewaysManager = new GatewaysManager(context, preferences); + gatewaysManager.addFromString(gatewaysString); preferences.edit().remove(Gateway.TAG).apply(); } private void gatewaysToPreferences() { - String gateways_string = gateways_manager.toString(); + String gateways_string = gatewaysManager.toString(); preferences.edit().putString(Gateway.TAG, gateways_string).commit(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java index 43c16d00..0b330ed9 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java @@ -36,9 +36,11 @@ import java.util.List; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.Connection; import de.blinkt.openvpn.core.ProfileManager; -import se.leap.bitmaskclient.Constants; import se.leap.bitmaskclient.Provider; +import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY; +import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; + /** * @author parmegv */ @@ -124,8 +126,8 @@ public class GatewaysManager { JSONObject result = new JSONObject(); try { result.put(Provider.CA_CERT, preferences.getString(Provider.CA_CERT, "")); - result.put(Constants.PROVIDER_PRIVATE_KEY, preferences.getString(Constants.PROVIDER_PRIVATE_KEY, "")); - result.put(Constants.PROVIDER_VPN_CERTIFICATE, preferences.getString(Constants.PROVIDER_VPN_CERTIFICATE, "")); + result.put(PROVIDER_PRIVATE_KEY, preferences.getString(PROVIDER_PRIVATE_KEY, "")); + result.put(PROVIDER_VPN_CERTIFICATE, preferences.getString(PROVIDER_VPN_CERTIFICATE, "")); } catch (JSONException e) { e.printStackTrace(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java index 28099f06..197a080b 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnCertificateValidator.java @@ -16,10 +16,13 @@ */ package se.leap.bitmaskclient.eip; -import java.security.cert.*; -import java.util.*; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.Calendar; +import java.util.Date; -import se.leap.bitmaskclient.*; +import se.leap.bitmaskclient.ConfigHelper; public class VpnCertificateValidator { public final static String TAG = VpnCertificateValidator.class.getSimpleName(); diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java index 54563ec4..6f0ccf18 100644 --- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java +++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java @@ -16,11 +16,16 @@ */ package se.leap.bitmaskclient.eip; -import org.json.*; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; -import java.util.*; +import java.util.Iterator; -import se.leap.bitmaskclient.*; +import se.leap.bitmaskclient.Provider; + +import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY; +import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; public class VpnConfigGenerator { @@ -29,7 +34,7 @@ public class VpnConfigGenerator { private JSONObject secrets; public final static String TAG = VpnConfigGenerator.class.getSimpleName(); - private final String new_line = System.getProperty("line.separator"); // Platform new line + private final String newLine = System.getProperty("line.separator"); // Platform new line public VpnConfigGenerator(JSONObject general_configuration, JSONObject secrets, JSONObject gateway) { this.general_configuration = general_configuration; @@ -40,25 +45,25 @@ public class VpnConfigGenerator { public String generate() { return generalConfiguration() - + new_line + + newLine + gatewayConfiguration() - + new_line + + newLine + secretsConfiguration() - + new_line + + newLine + androidCustomizations(); } private String generalConfiguration() { - String common_options = ""; + String commonOptions = ""; try { Iterator keys = general_configuration.keys(); while (keys.hasNext()) { String key = keys.next().toString(); - common_options += key + " "; + commonOptions += key + " "; for (String word : String.valueOf(general_configuration.get(key)).split(" ")) - common_options += word + " "; - common_options += new_line; + commonOptions += word + " "; + commonOptions += newLine; } } catch (JSONException e) { @@ -66,31 +71,31 @@ public class VpnConfigGenerator { e.printStackTrace(); } - common_options += "client"; + commonOptions += "client"; - return common_options; + return commonOptions; } private String gatewayConfiguration() { String remotes = ""; - String ip_address_keyword = "ip_address"; - String remote_keyword = "remote"; - String ports_keyword = "ports"; - String protocol_keyword = "protocols"; - String capabilities_keyword = "capabilities"; + String ipAddressKeyword = "ip_address"; + String remoteKeyword = "remote"; + String portsKeyword = "ports"; + String protocolKeyword = "protocols"; + String capabilitiesKeyword = "capabilities"; try { - String ip_address = gateway.getString(ip_address_keyword); - JSONObject capabilities = gateway.getJSONObject(capabilities_keyword); - JSONArray ports = capabilities.getJSONArray(ports_keyword); + String ip_address = gateway.getString(ipAddressKeyword); + JSONObject capabilities = gateway.getJSONObject(capabilitiesKeyword); + JSONArray ports = capabilities.getJSONArray(portsKeyword); for (int i = 0; i < ports.length(); i++) { String port_specific_remotes = ""; int port = ports.getInt(i); - JSONArray protocols = capabilities.getJSONArray(protocol_keyword); + JSONArray protocols = capabilities.getJSONArray(protocolKeyword); for (int j = 0; j < protocols.length(); j++) { String protocol = protocols.optString(j); - String new_remote = remote_keyword + " " + ip_address + " " + port + " " + protocol + new_line; + String new_remote = remoteKeyword + " " + ip_address + " " + port + " " + protocol + newLine; port_specific_remotes += new_remote; } @@ -100,8 +105,8 @@ public class VpnConfigGenerator { // TODO Auto-generated catch block e.printStackTrace(); } - if (remotes.endsWith(new_line)) { - remotes = remotes.substring(0, remotes.lastIndexOf(new_line)); + if (remotes.endsWith(newLine)) { + remotes = remotes.substring(0, remotes.lastIndexOf(newLine)); } return remotes; } @@ -110,26 +115,26 @@ public class VpnConfigGenerator { try { String ca = "<ca>" - + new_line + + newLine + secrets.getString(Provider.CA_CERT) - + new_line + + newLine + "</ca>"; String key = "<key>" - + new_line - + secrets.getString(Constants.PROVIDER_PRIVATE_KEY) - + new_line + + newLine + + secrets.getString(PROVIDER_PRIVATE_KEY) + + newLine + "</key>"; - String openvpn_cert = + String openvpnCert = "<cert>" - + new_line - + secrets.getString(Constants.PROVIDER_VPN_CERTIFICATE) - + new_line + + newLine + + secrets.getString(PROVIDER_VPN_CERTIFICATE) + + newLine + "</cert>"; - return ca + new_line + key + new_line + openvpn_cert; + return ca + newLine + key + newLine + openvpnCert; } catch (JSONException e) { e.printStackTrace(); return ""; @@ -139,9 +144,9 @@ public class VpnConfigGenerator { private String androidCustomizations() { return "remote-cert-tls server" - + new_line + + newLine + "persist-tun" - + new_line + + newLine + "auth-retry nointeract"; } } diff --git a/app/src/main/java/se/leap/bitmaskclient/fragments/AboutFragment.java b/app/src/main/java/se/leap/bitmaskclient/fragments/AboutFragment.java new file mode 100644 index 00000000..113ce397 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/fragments/AboutFragment.java @@ -0,0 +1,49 @@ +package se.leap.bitmaskclient.fragments; + +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import butterknife.ButterKnife; +import butterknife.InjectView; +import se.leap.bitmaskclient.R; + +public class AboutFragment extends Fragment { + + final public static String TAG = "aboutFragment"; + final public static int VIEWED = 0; + + @InjectView(R.id.version) + TextView versionTextView; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.about, container, false); + ButterKnife.inject(this, view); + return view; + } + + @Override + public void onStart() { + super.onStart(); + String version; + String name = "Bitmask"; + try { + PackageInfo packageinfo = getActivity().getPackageManager().getPackageInfo( + getActivity().getPackageName(), 0); + version = packageinfo.versionName; + name = getString(R.string.app_name); + } catch (NameNotFoundException e) { + version = "error fetching version"; + } + + versionTextView.setText(getString(R.string.version_info, name, version)); + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/fragments/LogFragment.java b/app/src/main/java/se/leap/bitmaskclient/fragments/LogFragment.java new file mode 100644 index 00000000..3d85b4ad --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/fragments/LogFragment.java @@ -0,0 +1,686 @@ +/* + * Copyright (c) 2012-2016 Arne Schwabe + * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + */ + +package se.leap.bitmaskclient.fragments; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ObjectAnimator; +import android.app.Activity; +import android.app.AlertDialog; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.database.DataSetObserver; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.os.Handler.Callback; +import android.os.Message; +import android.preference.PreferenceManager; +import android.support.annotation.Nullable; +import android.support.v4.app.ListFragment; +import android.text.SpannableString; +import android.text.format.DateFormat; +import android.text.style.ImageSpan; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.LinearLayout; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.RadioGroup; +import android.widget.SeekBar; +import android.widget.TextView; +import android.widget.Toast; + +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.Locale; +import java.util.Vector; + +import de.blinkt.openvpn.LaunchVPN; +import de.blinkt.openvpn.VpnProfile; +import de.blinkt.openvpn.core.ConnectionStatus; +import de.blinkt.openvpn.core.LogItem; +import de.blinkt.openvpn.core.OpenVPNManagement; +import de.blinkt.openvpn.core.OpenVPNService; +import de.blinkt.openvpn.core.Preferences; +import de.blinkt.openvpn.core.ProfileManager; +import de.blinkt.openvpn.core.VpnStatus; +import de.blinkt.openvpn.core.VpnStatus.LogListener; +import de.blinkt.openvpn.core.VpnStatus.StateListener; +import se.leap.bitmaskclient.Dashboard; +import se.leap.bitmaskclient.R; + +import static de.blinkt.openvpn.core.OpenVPNService.humanReadableByteCount; + +public class LogFragment extends ListFragment implements StateListener, SeekBar.OnSeekBarChangeListener, RadioGroup.OnCheckedChangeListener, VpnStatus.ByteCountListener { + private static final String LOGTIMEFORMAT = "logtimeformat"; + private static final int START_VPN_CONFIG = 0; + private static final String VERBOSITYLEVEL = "verbositylevel"; + + + + private SeekBar mLogLevelSlider; + private LinearLayout mOptionsLayout; + private RadioGroup mTimeRadioGroup; + private TextView mUpStatus; + private TextView mDownStatus; + private TextView mConnectStatus; + private boolean mShowOptionsLayout; + private CheckBox mClearLogCheckBox; + + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + ladapter.setLogLevel(progress + 1); + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + } + + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + switch (checkedId) { + case R.id.radioISO: + ladapter.setTimeFormat(LogWindowListAdapter.TIME_FORMAT_ISO); + break; + case R.id.radioNone: + ladapter.setTimeFormat(LogWindowListAdapter.TIME_FORMAT_NONE); + break; + case R.id.radioShort: + ladapter.setTimeFormat(LogWindowListAdapter.TIME_FORMAT_SHORT); + break; + + } + } + + @Override + public void updateByteCount(long in, long out, long diffIn, long diffOut) { + //%2$s/s %1$s - ↑%4$s/s %3$s + Resources res = getActivity().getResources(); + final String down = String.format("%2$s %1$s", humanReadableByteCount(in, false, res), humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true, res)); + final String up = String.format("%2$s %1$s", humanReadableByteCount(out, false, res), humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true, res)); + + if (mUpStatus != null && mDownStatus != null) { + if (getActivity() != null) { + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + mUpStatus.setText(up); + mDownStatus.setText(down); + } + }); + } + } + + } + + + class LogWindowListAdapter implements ListAdapter, LogListener, Callback { + + private static final int MESSAGE_NEWLOG = 0; + + private static final int MESSAGE_CLEARLOG = 1; + + private static final int MESSAGE_NEWTS = 2; + private static final int MESSAGE_NEWLOGLEVEL = 3; + + public static final int TIME_FORMAT_NONE = 0; + public static final int TIME_FORMAT_SHORT = 1; + public static final int TIME_FORMAT_ISO = 2; + private static final int MAX_STORED_LOG_ENTRIES = 1000; + + private Vector<LogItem> allEntries = new Vector<>(); + + private Vector<LogItem> currentLevelEntries = new Vector<LogItem>(); + + private Handler mHandler; + + private Vector<DataSetObserver> observers = new Vector<DataSetObserver>(); + + private int mTimeFormat = 0; + private int mLogLevel = 3; + + + public LogWindowListAdapter() { + initLogBuffer(); + if (mHandler == null) { + mHandler = new Handler(this); + } + + VpnStatus.addLogListener(this); + } + + + private void initLogBuffer() { + allEntries.clear(); + Collections.addAll(allEntries, VpnStatus.getlogbuffer()); + initCurrentMessages(); + } + + String getLogStr() { + String str = ""; + for (LogItem entry : allEntries) { + str += getTime(entry, TIME_FORMAT_ISO) + entry.getString(getActivity()) + '\n'; + } + return str; + } + + + private void shareLog() { + Intent shareIntent = new Intent(Intent.ACTION_SEND); + shareIntent.putExtra(Intent.EXTRA_TEXT, getLogStr()); + shareIntent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.ics_openvpn_log_file)); + shareIntent.setType("text/plain"); + startActivity(Intent.createChooser(shareIntent, "Send Logfile")); + } + + @Override + public void registerDataSetObserver(DataSetObserver observer) { + observers.add(observer); + + } + + @Override + public void unregisterDataSetObserver(DataSetObserver observer) { + observers.remove(observer); + } + + @Override + public int getCount() { + return currentLevelEntries.size(); + } + + @Override + public Object getItem(int position) { + return currentLevelEntries.get(position); + } + + @Override + public long getItemId(int position) { + return ((Object) currentLevelEntries.get(position)).hashCode(); + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + TextView v; + if (convertView == null) + v = new TextView(getActivity()); + else + v = (TextView) convertView; + + LogItem le = currentLevelEntries.get(position); + String msg = le.getString(getActivity()); + String time = getTime(le, mTimeFormat); + msg = time + msg; + + int spanStart = time.length(); + + SpannableString t = new SpannableString(msg); + + //t.setSpan(getSpanImage(le,(int)v.getTextSize()),spanStart,spanStart+1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); + v.setText(t); + return v; + } + + private String getTime(LogItem le, int time) { + if (time != TIME_FORMAT_NONE) { + Date d = new Date(le.getLogtime()); + java.text.DateFormat timeformat; + if (time == TIME_FORMAT_ISO) + timeformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); + else + timeformat = DateFormat.getTimeFormat(getActivity()); + + return timeformat.format(d) + " "; + + } else { + return ""; + } + + } + + private ImageSpan getSpanImage(LogItem li, int imageSize) { + int imageRes = android.R.drawable.ic_menu_call; + + switch (li.getLogLevel()) { + case ERROR: + imageRes = android.R.drawable.ic_notification_clear_all; + break; + case INFO: + imageRes = android.R.drawable.ic_menu_compass; + break; + case VERBOSE: + imageRes = android.R.drawable.ic_menu_info_details; + break; + case WARNING: + imageRes = android.R.drawable.ic_menu_camera; + break; + } + + Drawable d = getResources().getDrawable(imageRes); + + + //d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); + d.setBounds(0, 0, imageSize, imageSize); + ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BOTTOM); + + return span; + } + + @Override + public int getItemViewType(int position) { + return 0; + } + + @Override + public int getViewTypeCount() { + return 1; + } + + @Override + public boolean isEmpty() { + return currentLevelEntries.isEmpty(); + + } + + @Override + public boolean areAllItemsEnabled() { + return true; + } + + @Override + public boolean isEnabled(int position) { + return true; + } + + @Override + public void newLog(LogItem logMessage) { + Message msg = Message.obtain(); + assert (msg != null); + msg.what = MESSAGE_NEWLOG; + Bundle bundle = new Bundle(); + bundle.putParcelable("logmessage", logMessage); + msg.setData(bundle); + mHandler.sendMessage(msg); + } + + @Override + public boolean handleMessage(Message msg) { + // We have been called + if (msg.what == MESSAGE_NEWLOG) { + + LogItem logMessage = msg.getData().getParcelable("logmessage"); + if (addLogMessage(logMessage)) + for (DataSetObserver observer : observers) { + observer.onChanged(); + } + } else if (msg.what == MESSAGE_CLEARLOG) { + for (DataSetObserver observer : observers) { + observer.onInvalidated(); + } + initLogBuffer(); + } else if (msg.what == MESSAGE_NEWTS) { + for (DataSetObserver observer : observers) { + observer.onInvalidated(); + } + } else if (msg.what == MESSAGE_NEWLOGLEVEL) { + initCurrentMessages(); + + for (DataSetObserver observer : observers) { + observer.onChanged(); + } + + } + + return true; + } + + private void initCurrentMessages() { + currentLevelEntries.clear(); + for (LogItem li : allEntries) { + if (li.getVerbosityLevel() <= mLogLevel || + mLogLevel == VpnProfile.MAXLOGLEVEL) + currentLevelEntries.add(li); + } + } + + /** + * @param logmessage + * @return True if the current entries have changed + */ + private boolean addLogMessage(LogItem logmessage) { + allEntries.add(logmessage); + + if (allEntries.size() > MAX_STORED_LOG_ENTRIES) { + Vector<LogItem> oldAllEntries = allEntries; + allEntries = new Vector<LogItem>(allEntries.size()); + for (int i = 50; i < oldAllEntries.size(); i++) { + allEntries.add(oldAllEntries.elementAt(i)); + } + initCurrentMessages(); + return true; + } else { + if (logmessage.getVerbosityLevel() <= mLogLevel) { + currentLevelEntries.add(logmessage); + return true; + } else { + return false; + } + } + } + + void clearLog() { + // Actually is probably called from GUI Thread as result of the user + // pressing a button. But better safe than sorry + VpnStatus.clearLog(); + VpnStatus.logInfo(R.string.logCleared); + mHandler.sendEmptyMessage(MESSAGE_CLEARLOG); + } + + + public void setTimeFormat(int newTimeFormat) { + mTimeFormat = newTimeFormat; + mHandler.sendEmptyMessage(MESSAGE_NEWTS); + } + + public void setLogLevel(int logLevel) { + mLogLevel = logLevel; + mHandler.sendEmptyMessage(MESSAGE_NEWLOGLEVEL); + } + + } + + + private LogWindowListAdapter ladapter; + private TextView mSpeedView; + + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.clearlog) { + ladapter.clearLog(); + return true; + } else if (item.getItemId() == R.id.send) { + ladapter.shareLog(); + } else if (item.getItemId() == R.id.edit_vpn) { + VpnProfile lastConnectedprofile = ProfileManager.get(getActivity(), VpnStatus.getLastConnectedVPNProfile()); + + if (lastConnectedprofile != null) { + Intent vprefintent = new Intent(getActivity(), Dashboard.class) + .putExtra(VpnProfile.EXTRA_PROFILEUUID, lastConnectedprofile.getUUIDString()); + startActivityForResult(vprefintent, START_VPN_CONFIG); + } else { + Toast.makeText(getActivity(), R.string.log_no_last_vpn, Toast.LENGTH_LONG).show(); + } + } else if (item.getItemId() == R.id.toggle_time) { + showHideOptionsPanel(); + } else if (item.getItemId() == android.R.id.home) { + // This is called when the Home (Up) button is pressed + // in the Action Bar. + Intent parentActivityIntent = new Intent(getActivity(), Dashboard.class); + parentActivityIntent.addFlags( + Intent.FLAG_ACTIVITY_CLEAR_TOP | + Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(parentActivityIntent); + getActivity().finish(); + return true; + + } + return super.onOptionsItemSelected(item); + + } + + private void showHideOptionsPanel() { + boolean optionsVisible = (mOptionsLayout.getVisibility() != View.GONE); + + ObjectAnimator anim; + if (optionsVisible) { + anim = ObjectAnimator.ofFloat(mOptionsLayout, "alpha", 1.0f, 0f); + anim.addListener(collapseListener); + + } else { + mOptionsLayout.setVisibility(View.VISIBLE); + anim = ObjectAnimator.ofFloat(mOptionsLayout, "alpha", 0f, 1.0f); + //anim = new TranslateAnimation(0.0f, 0.0f, mOptionsLayout.getHeight(), 0.0f); + + } + + //anim.setInterpolator(new AccelerateInterpolator(1.0f)); + //anim.setDuration(300); + //mOptionsLayout.startAnimation(anim); + anim.start(); + + } + + AnimatorListenerAdapter collapseListener = new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + mOptionsLayout.setVisibility(View.GONE); + } + + }; + + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.f_log, menu); + if (getResources().getBoolean(R.bool.logSildersAlwaysVisible)) + menu.removeItem(R.id.toggle_time); + } + + + @Override + public void onResume() { + super.onResume(); + Intent intent = new Intent(getActivity(), OpenVPNService.class); + intent.setAction(OpenVPNService.START_SERVICE); + } + + @Override + public void onStart() { + super.onStart(); + VpnStatus.addStateListener(this); + VpnStatus.addByteCountListener(this); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == START_VPN_CONFIG && resultCode == Activity.RESULT_OK) { + String configuredVPN = data.getStringExtra(VpnProfile.EXTRA_PROFILEUUID); + + final VpnProfile profile = ProfileManager.get(getActivity(), configuredVPN); + ProfileManager.getInstance(getActivity()).saveProfile(getActivity(), profile); + // Name could be modified, reset List adapter + + AlertDialog.Builder dialog = new AlertDialog.Builder(getActivity()); + dialog.setTitle(R.string.configuration_changed); + dialog.setMessage(R.string.restart_vpn_after_change); + + + dialog.setPositiveButton(R.string.restart, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Intent intent = new Intent(getActivity(), LaunchVPN.class); + intent.putExtra(LaunchVPN.EXTRA_KEY, profile.getUUIDString()); + intent.setAction(Intent.ACTION_MAIN); + startActivity(intent); + } + + + }); + dialog.setNegativeButton(R.string.ignore, null); + dialog.create().show(); + } + super.onActivityResult(requestCode, resultCode, data); + } + + + @Override + public void onStop() { + super.onStop(); + VpnStatus.removeStateListener(this); + VpnStatus.removeByteCountListener(this); + + getActivity().getPreferences(0).edit().putInt(LOGTIMEFORMAT, ladapter.mTimeFormat) + .putInt(VERBOSITYLEVEL, ladapter.mLogLevel).apply(); + + } + + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + ListView lv = getListView(); + + lv.setOnItemLongClickListener(new OnItemLongClickListener() { + + @Override + public boolean onItemLongClick(AdapterView<?> parent, View view, + int position, long id) { + ClipboardManager clipboard = (ClipboardManager) + getActivity().getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("Log Entry", ((TextView) view).getText()); + clipboard.setPrimaryClip(clip); + Toast.makeText(getActivity(), R.string.copied_entry, Toast.LENGTH_SHORT).show(); + return true; + } + }); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View v = inflater.inflate(R.layout.f_log, container, false); + + setHasOptionsMenu(true); + + ladapter = new LogWindowListAdapter(); + ladapter.mTimeFormat = getActivity().getPreferences(0).getInt(LOGTIMEFORMAT, 1); + int logLevel = getActivity().getPreferences(0).getInt(VERBOSITYLEVEL, 1); + ladapter.setLogLevel(logLevel); + + setListAdapter(ladapter); + + mTimeRadioGroup = (RadioGroup) v.findViewById(R.id.timeFormatRadioGroup); + mTimeRadioGroup.setOnCheckedChangeListener(this); + + if (ladapter.mTimeFormat == LogWindowListAdapter.TIME_FORMAT_ISO) { + mTimeRadioGroup.check(R.id.radioISO); + } else if (ladapter.mTimeFormat == LogWindowListAdapter.TIME_FORMAT_NONE) { + mTimeRadioGroup.check(R.id.radioNone); + } else if (ladapter.mTimeFormat == LogWindowListAdapter.TIME_FORMAT_SHORT) { + mTimeRadioGroup.check(R.id.radioShort); + } + + mClearLogCheckBox = (CheckBox) v.findViewById(R.id.clearlogconnect); + mClearLogCheckBox.setChecked(PreferenceManager.getDefaultSharedPreferences(getActivity()).getBoolean(LaunchVPN.CLEARLOG, true)); + mClearLogCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + Preferences.getDefaultSharedPreferences(getActivity()).edit().putBoolean(LaunchVPN.CLEARLOG, isChecked).apply(); + } + }); + + mSpeedView = (TextView) v.findViewById(R.id.speed); + + mOptionsLayout = (LinearLayout) v.findViewById(R.id.logOptionsLayout); + mLogLevelSlider = (SeekBar) v.findViewById(R.id.LogLevelSlider); + mLogLevelSlider.setMax(VpnProfile.MAXLOGLEVEL - 1); + mLogLevelSlider.setProgress(logLevel - 1); + + mLogLevelSlider.setOnSeekBarChangeListener(this); + + if (getResources().getBoolean(R.bool.logSildersAlwaysVisible)) + mOptionsLayout.setVisibility(View.VISIBLE); + + mUpStatus = (TextView) v.findViewById(R.id.speedUp); + mDownStatus = (TextView) v.findViewById(R.id.speedDown); + mConnectStatus = (TextView) v.findViewById(R.id.speedStatus); + if (mShowOptionsLayout) + mOptionsLayout.setVisibility(View.VISIBLE); + return v; + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + // Scroll to the end of the list end + //getListView().setSelection(getListView().getAdapter().getCount()-1); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (getResources().getBoolean(R.bool.logSildersAlwaysVisible)) { + mShowOptionsLayout = true; + if (mOptionsLayout != null) + mOptionsLayout.setVisibility(View.VISIBLE); + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + } + + + @Override + public void updateState(final String status, final String logMessage, final int resId, final ConnectionStatus level) { + if (isAdded()) { + final String cleanLogMessage = VpnStatus.getLastCleanLogMessage(getActivity()); + + getActivity().runOnUiThread(new Runnable() { + + @Override + public void run() { + if (isAdded()) { + if (mSpeedView != null) { + mSpeedView.setText(cleanLogMessage); + } + if (mConnectStatus != null) + mConnectStatus.setText(cleanLogMessage); + } + } + }); + } + } + + @Override + public void setConnectedVPN(String uuid) { + } + + + @Override + public void onDestroy() { + VpnStatus.removeLogListener(ladapter); + super.onDestroy(); + } + +} diff --git a/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java b/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java index bd9324bb..29d4f01d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/userstatus/SessionDialog.java @@ -16,13 +16,19 @@ */ package se.leap.bitmaskclient.userstatus; -import android.app.*; -import android.content.*; -import android.os.*; -import android.view.*; -import android.widget.*; - -import butterknife.*; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AppCompatActivity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; +import android.widget.TextView; + +import butterknife.ButterKnife; +import butterknife.InjectView; import se.leap.bitmaskclient.Provider; import se.leap.bitmaskclient.R; @@ -45,18 +51,18 @@ public class SessionDialog extends DialogFragment { final public static String USERNAME = "username"; final public static String PASSWORD = "password"; - public static enum ERRORS { + public enum ERRORS { USERNAME_MISSING, PASSWORD_INVALID_LENGTH, RISEUP_WARNING } @InjectView(R.id.user_message) - TextView user_message; + TextView userMessage; @InjectView(R.id.username_entered) - EditText username_field; + EditText usernameField; @InjectView(R.id.password_entered) - EditText password_field; + EditText passwordField; public static SessionDialog getInstance(Provider provider, Bundle arguments) { SessionDialog dialog = new SessionDialog(); @@ -112,35 +118,35 @@ public class SessionDialog extends DialogFragment { private void setUp(Bundle arguments) { if (arguments.containsKey(ERRORS.PASSWORD_INVALID_LENGTH.toString())) - password_field.setError(getString(R.string.error_not_valid_password_user_message)); + passwordField.setError(getString(R.string.error_not_valid_password_user_message)); else if (arguments.containsKey(ERRORS.RISEUP_WARNING.toString())) { - user_message.setVisibility(VISIBLE); - user_message.setText(R.string.login_riseup_warning); + userMessage.setVisibility(VISIBLE); + userMessage.setText(R.string.login_riseup_warning); } if (arguments.containsKey(USERNAME)) { String username = arguments.getString(USERNAME); - username_field.setText(username); + usernameField.setText(username); } if (arguments.containsKey(ERRORS.USERNAME_MISSING.toString())) { - username_field.setError(getString(R.string.username_ask)); + usernameField.setError(getString(R.string.username_ask)); } if (arguments.containsKey(getString(R.string.user_message))) { - user_message.setText(arguments.getString(getString(R.string.user_message))); - user_message.setVisibility(VISIBLE); - } else if (user_message.getVisibility() != VISIBLE) - user_message.setVisibility(View.GONE); + userMessage.setText(arguments.getString(getString(R.string.user_message))); + userMessage.setVisibility(VISIBLE); + } else if (userMessage.getVisibility() != VISIBLE) + userMessage.setVisibility(View.GONE); - if (!username_field.getText().toString().isEmpty() && password_field.isFocusable()) - password_field.requestFocus(); + if (!usernameField.getText().toString().isEmpty() && passwordField.isFocusable()) + passwordField.requestFocus(); } private String getEnteredUsername() { - return username_field.getText().toString(); + return usernameField.getText().toString(); } private String getEnteredPassword() { - return password_field.getText().toString(); + return passwordField.getText().toString(); } @@ -150,22 +156,22 @@ public class SessionDialog extends DialogFragment { * @author parmegv */ public interface SessionDialogInterface { - public void logIn(String username, String password); + void logIn(String username, String password); - public void signUp(String username, String password); + void signUp(String username, String password); } SessionDialogInterface interface_with_Dashboard; @Override - public void onAttach(Activity activity) { - super.onAttach(activity); + public void onAttach(Context context) { + super.onAttach(context); try { - interface_with_Dashboard = (SessionDialogInterface) activity.getFragmentManager().findFragmentById(R.id.user_status_fragment);; + interface_with_Dashboard = (SessionDialogInterface) ((AppCompatActivity) context).getSupportFragmentManager().getFragments().get(0); } catch (ClassCastException e) { - throw new ClassCastException(activity.toString() + throw new ClassCastException(context.toString() + " must implement LogInDialogListener"); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatusFragment.java b/app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatusFragment.java index 14323f8e..0f1d0cdb 100644 --- a/app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatusFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/userstatus/UserStatusFragment.java @@ -1,9 +1,8 @@ package se.leap.bitmaskclient.userstatus; -import android.app.Activity; -import android.app.Fragment; +import android.content.Context; import android.os.Bundle; -import android.os.Handler; +import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -18,7 +17,7 @@ import java.util.Observer; import butterknife.ButterKnife; import butterknife.InjectView; import butterknife.OnClick; -import se.leap.bitmaskclient.Dashboard; +import se.leap.bitmaskclient.MainActivity; import se.leap.bitmaskclient.Provider; import se.leap.bitmaskclient.ProviderAPI; import se.leap.bitmaskclient.ProviderAPICommand; @@ -28,7 +27,6 @@ import se.leap.bitmaskclient.R; public class UserStatusFragment extends Fragment implements Observer, SessionDialog.SessionDialogInterface { public static String TAG = UserStatusFragment.class.getSimpleName(); - private static Dashboard dashboard; private ProviderAPIResultReceiver providerAPI_result_receiver; @InjectView(R.id.user_status_username) @@ -39,7 +37,7 @@ public class UserStatusFragment extends Fragment implements Observer, SessionDia Button button; private UserStatus status; - private boolean allows_registration = false; + private boolean allowsRegistration = false; @Override public void onCreate(Bundle savedInstanceState) { @@ -65,25 +63,22 @@ public class UserStatusFragment extends Fragment implements Observer, SessionDia ButterKnife.inject(this, view); Bundle arguments = getArguments(); - allows_registration = arguments.getBoolean(Provider.ALLOW_REGISTRATION); + allowsRegistration = arguments.getBoolean(Provider.ALLOW_REGISTRATION); handleNewStatus(status); return view; } @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - dashboard = (Dashboard) activity; - - providerAPI_result_receiver = new ProviderAPIResultReceiver(new Handler(), dashboard); + public void onAttach(Context context) { + super.onAttach(context); } public void restoreSessionStatus(Bundle savedInstanceState) { if (savedInstanceState != null) if (savedInstanceState.containsKey(UserStatus.TAG)) { UserStatus.SessionStatus status = (UserStatus.SessionStatus) savedInstanceState.getSerializable(UserStatus.TAG); - this.status.updateStatus(status, getResources()); + UserStatus.updateStatus(status, getResources()); } } @@ -93,7 +88,7 @@ public class UserStatusFragment extends Fragment implements Observer, SessionDia if(status.isLoggedIn()) logOut(); else if(status.isLoggedOut()) - dashboard.sessionDialog(Bundle.EMPTY); + MainActivity.sessionDialog(Bundle.EMPTY); else if(status.inProgress()) cancelLoginOrSignup(); } @@ -102,7 +97,7 @@ public class UserStatusFragment extends Fragment implements Observer, SessionDia public void update(Observable observable, Object data) { if (observable instanceof UserStatus) { final UserStatus status = (UserStatus) observable; - dashboard.runOnUiThread(new Runnable() { + getActivity().runOnUiThread(new Runnable() { @Override public void run() { handleNewStatus(status); @@ -113,7 +108,7 @@ public class UserStatusFragment extends Fragment implements Observer, SessionDia private void handleNewStatus(UserStatus status) { this.status = status; - if (allows_registration) { + if (allowsRegistration) { if (this.status.inProgress()) showUserSessionProgressBar(); else @@ -138,12 +133,12 @@ public class UserStatusFragment extends Fragment implements Observer, SessionDia private void updateButton() { if(status.isLoggedIn() || status.didntLogOut()) - button.setText(dashboard.getString(R.string.logout_button)); - else if(allows_registration) { + button.setText(getActivity().getString(R.string.logout_button)); + else if(allowsRegistration) { if (status.isLoggedOut() || status.notLoggedIn()) - button.setText(dashboard.getString(R.string.login_button)); + button.setText(getActivity().getString(R.string.login_button)); else if (status.inProgress()) - button.setText(dashboard.getString(android.R.string.cancel)); + button.setText(getActivity().getString(android.R.string.cancel)); } } diff --git a/app/src/main/res/drawable-hdpi/drawer_shadow.9.png b/app/src/main/res/drawable-hdpi/drawer_shadow.9.png Binary files differnew file mode 100644 index 00000000..236bff55 --- /dev/null +++ b/app/src/main/res/drawable-hdpi/drawer_shadow.9.png diff --git a/app/src/main/res/drawable-hdpi/ic_drawer.png b/app/src/main/res/drawable-hdpi/ic_drawer.png Binary files differnew file mode 100644 index 00000000..c59f601c --- /dev/null +++ b/app/src/main/res/drawable-hdpi/ic_drawer.png diff --git a/app/src/main/res/drawable-mdpi/drawer_shadow.9.png b/app/src/main/res/drawable-mdpi/drawer_shadow.9.png Binary files differnew file mode 100644 index 00000000..ffe3a28d --- /dev/null +++ b/app/src/main/res/drawable-mdpi/drawer_shadow.9.png diff --git a/app/src/main/res/drawable-mdpi/ic_drawer.png b/app/src/main/res/drawable-mdpi/ic_drawer.png Binary files differnew file mode 100644 index 00000000..1ed2c56e --- /dev/null +++ b/app/src/main/res/drawable-mdpi/ic_drawer.png diff --git a/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png b/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png Binary files differnew file mode 100644 index 00000000..fabe9d96 --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png diff --git a/app/src/main/res/drawable-xhdpi/ic_drawer.png b/app/src/main/res/drawable-xhdpi/ic_drawer.png Binary files differnew file mode 100644 index 00000000..a5fa74de --- /dev/null +++ b/app/src/main/res/drawable-xhdpi/ic_drawer.png diff --git a/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png b/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png Binary files differnew file mode 100644 index 00000000..b91e9d7f --- /dev/null +++ b/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_drawer.png b/app/src/main/res/drawable-xxhdpi/ic_drawer.png Binary files differnew file mode 100644 index 00000000..9c4685d6 --- /dev/null +++ b/app/src/main/res/drawable-xxhdpi/ic_drawer.png diff --git a/app/src/main/res/layout-sw600dp-port/f_log.xml b/app/src/main/res/layout-sw600dp-port/f_log.xml new file mode 100644 index 00000000..da46450c --- /dev/null +++ b/app/src/main/res/layout-sw600dp-port/f_log.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (c) 2012-2016 Arne Schwabe + ~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="@dimen/activity_margin"> + +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout-sw600dp/f_log.xml b/app/src/main/res/layout-sw600dp/f_log.xml new file mode 100644 index 00000000..9ad30208 --- /dev/null +++ b/app/src/main/res/layout-sw600dp/f_log.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (c) 2012-2016 Arne Schwabe + ~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + android:padding="20dp" > + + + <LinearLayout + android:background="@drawable/white_rect" + android:elevation="1dp" + android:minWidth="300dp" + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="match_parent"> + + <include layout="@layout/log_silders"/> + + <include layout="@layout/vpnstatus"/> + </LinearLayout> + + <ListView + android:id="@android:id/list" + android:transcriptMode="normal" + android:layout_width="fill_parent" + android:layout_height="match_parent"/> +</LinearLayout> diff --git a/app/src/main/res/layout-xlarge/provider_detail_fragment.xml b/app/src/main/res/layout-xlarge/provider_detail_fragment.xml index 4abbaa17..860f99d9 100644 --- a/app/src/main/res/layout-xlarge/provider_detail_fragment.xml +++ b/app/src/main/res/layout-xlarge/provider_detail_fragment.xml @@ -40,4 +40,9 @@ android:textStyle="normal" android:textAppearance="?android:attr/textAppearanceSmall" /> + <ListView + android:id="@+id/provider_detail_options" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + </LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/a_login.xml b/app/src/main/res/layout/a_login.xml new file mode 100644 index 00000000..5ecb807c --- /dev/null +++ b/app/src/main/res/layout/a_login.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="match_parent"> + + <include + layout="@layout/provider_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <include layout="@layout/provider_credentials_login" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <Button + android:id="@+id/button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/login_button" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true"/> + + </RelativeLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/a_signup.xml b/app/src/main/res/layout/a_signup.xml new file mode 100644 index 00000000..edcaea45 --- /dev/null +++ b/app/src/main/res/layout/a_signup.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="match_parent"> + + <include + layout="@layout/provider_header" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <include layout="@layout/provider_credentials_signup" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <Button + android:id="@+id/button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/login_button" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true"/> + + </RelativeLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..de06efc7 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,42 @@ +<!-- A DrawerLayout is intended to be used as the top-level content view using match_parent for both width and height to consume the full space available. --> +<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/drawer_layout" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context="se.leap.bitmaskclient.MainActivity"> + + + <!-- As the main content view, the view below consumes the entire + space available using match_parent in both dimensions. --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <android.support.v7.widget.Toolbar + android:id="@+id/toolbar" + android:minHeight="?attr/actionBarSize" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:titleTextColor="@android:color/white" + android:background="?attr/colorPrimary"> + </android.support.v7.widget.Toolbar> + + <FrameLayout + android:id="@+id/container" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + </LinearLayout> + <!-- The drawer is given a fixed width in dp and extends the full height of + the container. --> + <fragment + android:id="@+id/navigation_drawer" + android:name="se.leap.bitmaskclient.drawer.NavigationDrawerFragment" + android:layout_width="@dimen/navigation_drawer_width" + android:layout_height="match_parent" + android:layout_gravity="start" + tools:layout="@layout/drawer_main" /> + +</android.support.v4.widget.DrawerLayout> diff --git a/app/src/main/res/layout/drawer_main.xml b/app/src/main/res/layout/drawer_main.xml new file mode 100644 index 00000000..20d826b3 --- /dev/null +++ b/app/src/main/res/layout/drawer_main.xml @@ -0,0 +1,54 @@ +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:background="@color/colorBackground" + tools:context="se.leap.bitmaskclient.drawer.NavigationDrawerFragment"> + + <FrameLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <android.support.v7.widget.AppCompatImageView + android:id="@+id/background" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:adjustViewBounds="false" + android:cropToPadding="false" + android:scaleType="fitXY" + app:srcCompat="@drawable/ic_colorsquare" /> + + <android.support.v7.widget.AppCompatImageView + android:id="@+id/mask" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:layout_centerInParent="true" + android:layout_centerVertical="true" + app:srcCompat="@drawable/mask" /> + + + </FrameLayout> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <ListView + android:id="@+id/accountList" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + + <ListView + android:id="@+id/settingsList" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + android:layout_alignParentBottom="true" /> + + </RelativeLayout> + +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/f_log.xml b/app/src/main/res/layout/f_log.xml new file mode 100644 index 00000000..41c72d99 --- /dev/null +++ b/app/src/main/res/layout/f_log.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (c) 2012-2016 Arne Schwabe + ~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="16dp"> + + <LinearLayout + android:elevation="1dp" + android:orientation="vertical" + android:layout_height="wrap_content" + android:layout_width="match_parent" > + + <include layout="@layout/f_log_sliders"/> + + <TextView + android:text="@string/speed_waiting" + android:singleLine="true" + android:id="@+id/speed" + tools:ignore="InconsistentLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + </LinearLayout> + + <ListView + android:id="@android:id/list" + android:transcriptMode="normal" + android:layout_width="fill_parent" + android:layout_height="fill_parent"/> + +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/f_log_sliders.xml b/app/src/main/res/layout/f_log_sliders.xml new file mode 100644 index 00000000..4196e243 --- /dev/null +++ b/app/src/main/res/layout/f_log_sliders.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="utf-8"?> + + +<!-- + ~ Copyright (c) 2012-2016 Arne Schwabe + ~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + --> + +<LinearLayout + xmlns:tools="http://schemas.android.com/tools" + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:id="@+id/logOptionsLayout" + android:visibility="gone" + tools:visibility="visible" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/log_verbosity_level"/> + + + <de.blinkt.openvpn.views.SeekBarTicks + android:id="@+id/LogLevelSlider" + android:layout_width="300dp" + android:layout_height="wrap_content" + tools:max="5" + android:indeterminate="false"/> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/timestamps"/> + + <RadioGroup + android:id="@+id/timeFormatRadioGroup" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <RadioButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/timestamps_none" + android:id="@+id/radioNone" + /> + + <RadioButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/timestamp_short" + android:id="@+id/radioShort" + /> + + <RadioButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/timestamp_iso" + android:id="@+id/radioISO" + /> + + + </RadioGroup> + + <CheckBox + tools:checked="true" + android:id="@+id/clearlogconnect" + android:text="@string/clear_log_on_connect" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> +</LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/fragment_main.xml b/app/src/main/res/layout/fragment_main.xml new file mode 100644 index 00000000..31dbd11e --- /dev/null +++ b/app/src/main/res/layout/fragment_main.xml @@ -0,0 +1,16 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingBottom="@dimen/activity_vertical_margin" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + tools:context="se.leap.bitmaskclient.MainActivity$PlaceholderFragment"> + + <TextView + android:id="@+id/section_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + +</RelativeLayout> diff --git a/app/src/main/res/layout/provider_credentials_login.xml b/app/src/main/res/layout/provider_credentials_login.xml new file mode 100644 index 00000000..9c52b9b5 --- /dev/null +++ b/app/src/main/res/layout/provider_credentials_login.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" > + + <android.support.design.widget.TextInputLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/auth_username" + app:errorEnabled="true"> + + <android.support.design.widget.TextInputEditText + android:id="@+id/provider_credentials_username" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:ems="10" + android:inputType="text" /> + + </android.support.design.widget.TextInputLayout> + + <android.support.design.widget.TextInputLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:passwordToggleEnabled="true" + android:hint="@string/password" + app:errorEnabled="true"> + + <android.support.design.widget.TextInputEditText + android:id="@+id/provider_credentials_password" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textPassword" + /> + + </android.support.design.widget.TextInputLayout> + +</merge>
\ No newline at end of file diff --git a/app/src/main/res/layout/provider_credentials_signup.xml b/app/src/main/res/layout/provider_credentials_signup.xml new file mode 100644 index 00000000..c2e3dcd3 --- /dev/null +++ b/app/src/main/res/layout/provider_credentials_signup.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" > + + <include layout="@layout/provider_credentials_login" /> + + <android.support.design.widget.TextInputLayout + android:id="@+id/provider_credentials_password_verification_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:passwordToggleEnabled="true" + android:hint="@string/password" + app:errorEnabled="true"> + + <android.support.design.widget.TextInputEditText + android:id="@+id/provider_credentials_password_verification" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textPassword" + /> + + </android.support.design.widget.TextInputLayout> + +</merge>
\ No newline at end of file diff --git a/app/src/main/res/layout/provider_detail_fragment.xml b/app/src/main/res/layout/provider_detail_fragment.xml index 3b35bae7..3db32b2c 100644 --- a/app/src/main/res/layout/provider_detail_fragment.xml +++ b/app/src/main/res/layout/provider_detail_fragment.xml @@ -38,4 +38,9 @@ android:textStyle="normal" android:textAppearance="?android:attr/textAppearanceSmall" /> + <ListView + android:id="@+id/provider_detail_options" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + </LinearLayout>
\ No newline at end of file diff --git a/app/src/main/res/layout/provider_header.xml b/app/src/main/res/layout/provider_header.xml new file mode 100644 index 00000000..89cbd7b2 --- /dev/null +++ b/app/src/main/res/layout/provider_header.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" > + + <ImageView + android:id="@+id/provider_header_logo" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:srcCompat="@drawable/mask" /> + + <TextView + android:id="@+id/provider_header_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="" /> + +</merge> diff --git a/app/src/main/res/menu/f_log.xml b/app/src/main/res/menu/f_log.xml new file mode 100644 index 00000000..d30b13cb --- /dev/null +++ b/app/src/main/res/menu/f_log.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (c) 2012-2016 Arne Schwabe + ~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt + --> + +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + + <item + android:id="@+id/toggle_time" + android:alphabeticShortcut="t" + android:icon="@drawable/ic_menu_view" + app:showAsAction="ifRoom" + android:title="@string/logview_options" /> + + <item + android:id="@+id/clearlog" + android:icon="@drawable/ic_menu_delete" + app:showAsAction="ifRoom" + android:title="@string/clear_log" + android:titleCondensed="@string/clear"/> + <item + android:id="@+id/send" + android:icon="@drawable/ic_menu_share" + app:showAsAction="ifRoom" + android:title="@string/send_logfile" + android:titleCondensed="@string/send"/> + +</menu> diff --git a/app/src/main/res/menu/main.xml b/app/src/main/res/menu/main.xml new file mode 100644 index 00000000..56f43959 --- /dev/null +++ b/app/src/main/res/menu/main.xml @@ -0,0 +1,13 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + tools:context="se.leap.bitmaskclient.MainActivity"> + <item + android:id="@+id/action_example" + android:showAsAction="withText|ifRoom" + android:title="@string/action_example" /> + <item + android:id="@+id/action_settings" + android:orderInCategory="100" + android:showAsAction="never" + android:title="@string/action_settings" /> +</menu> diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 192fb5a7..6355ce62 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -25,13 +25,13 @@ <string name="new_provider_uri">Dominio</string> <string name="valid_url_entered">El dominio está bien formado</string> <string name="not_valid_url_entered">Dominio no válido</string> - <string name="provider_details_fragment_title">Detalles del proveedor</string> + <string name="provider_details_title">Detalles del proveedor</string> <string name="use_anonymously_button">Utilizar sin registrarse</string> <string name="username_hint">usuario</string> <string name="username_ask">Por favor, introduce tu usuario</string> <string name="password_hint">contraseña</string> <string name="user_message">Mensaje al usuario</string> - <string name="title_about_activity">Acerca de Bitmask"</string> + <string name="about_fragment_title">Acerca de Bitmask"</string> <string name="error_srp_math_error_user_message">Inténtalo de nuevo: error en el servidor.</string> <string name="error_bad_user_password_user_message">Usuario o contraseña incorrectos.</string> <string name="error_not_valid_password_user_message">Al menos 8 caracteres.</string> diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 00000000..63fc8164 --- /dev/null +++ b/app/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ +<resources> + <!-- Example customization of dimensions originally defined in res/values/dimens.xml + (such as screen margins) for screens with more than 820dp of available width. This + would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). --> + <dimen name="activity_horizontal_margin">64dp</dimen> +</resources> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 1818312e..a8a63e4b 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -2,6 +2,9 @@ <resources> <color name="colorPrimary">#b39ddb</color> <color name="colorPrimaryDark">#ac97d2</color> + <color name="colorBackground">#fffafafa</color> + <color name="colorError">#ef9a9a</color> + <color name="colorSuccess">#a5d6a7</color> <color name="red200">#ef9a9a</color> <color name="pink200">#f48fb1</color> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index f8fafecd..3e06cbe2 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,5 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- +<?xml version="1.0" encoding="utf-8"?><!-- ~ Copyright (c) 2012-2016 Arne Schwabe ~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt --> @@ -17,4 +16,13 @@ <dimen name="round_button_diameter">56dp</dimen> <dimen name="switchbar_pad">16dp</dimen> <dimen name="vpn_setting_padding">16dp</dimen> + + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_margin">16dp</dimen> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> + + <!-- Per the design guidelines, navigation drawers should be between 240dp and 320dp: + https://developer.android.com/design/patterns/navigation-drawer.html --> + <dimen name="navigation_drawer_width">300dp</dimen> </resources>
\ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 75b690ac..9536d96e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -23,14 +23,17 @@ <string name="new_provider_uri">Domain name</string> <string name="valid_url_entered">The URL is valid</string> <string name="not_valid_url_entered">Malformed URL</string> - <string name="provider_details_fragment_title">Provider details</string> + <string name="provider_details_title">Provider details</string> <string name="use_anonymously_button">Use anonymously</string> <string name="username_hint">username</string> <string name="username_ask">Please enter your username</string> + <string name="password_ask">Please enter your password</string> <string name="password_hint">password</string> + <string name="password_match">Passwords match</string> + <string name="password_mismatch">Passwords do not match</string> <string name="user_message">User message</string> - <string name="title_about_activity">About</string> - <string name="error_srp_math_error_user_message">Try again: Server math error.</string> + <string name="about_fragment_title">About</string> + <string name="error_srp_math_error_user_message">Try again: Server math error.</string> <string name="error_bad_user_password_user_message">Incorrect username or password.</string> <string name="error_not_valid_password_user_message">It must be at least 8 characters long.</string> <string name="error_client_http_user_message">Try again: Client HTTP error</string> @@ -78,6 +81,13 @@ <string name="vpn.button.turn.off">Turn off</string> <string name="vpn_button_turn_off_blocking">Stop blocking</string> <string name="bitmask_log">Bitmask Log</string> + <string name="title_activity_main">Bitmask</string> + <string name="log_fragment_title">Log</string> + <string name="vpn_fragment_title">VPN</string> + <string name="navigation_drawer_open">Open navigation drawer</string> + <string name="navigation_drawer_close">Close navigation drawer</string> + <string name="action_example">Example action</string> + <string name="action_settings">Settings</string> <string name="void_vpn_establish">Bitmask blocks all outgoing internet traffic.</string> <string name="void_vpn_error_establish">Failed to establish blocking VPN.</string> <string name="void_vpn_stopped">Stopped blocking all outgoing internet traffic.</string> diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 4cdf2c00..b4e81bc4 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -59,4 +59,9 @@ <item name="android:windowFullscreen">true</item> <item name="android:windowContentOverlay">@null</item> </style> + + <style name="BitmaskButton" parent="android:Widget.Button"> + <item name="android:textAllCaps">true</item> + <item name="android:color">@color/colorPrimary</item> + </style> </resources>
\ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 80607409..76cfd866 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -1,12 +1,18 @@ <?xml version="1.0" encoding="utf-8"?> <resources> - <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> + <style name="BitmaskTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimary</item> + <item name="textColorError">@color/colorError</item> + + <item name="android:buttonStyle">@style/BitmaskButton</item> + </style> + <style name="SplashTheme" parent="Theme.AppCompat.NoActionBar"> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimary</item> <item name="android:windowBackground">@drawable/splash_page</item> </style> + </resources> |