summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/src/custom/java/se/leap/bitmaskclient/base/fragments/EipFragment.java686
-rw-r--r--app/src/custom/java/se/leap/bitmaskclient/base/views/MainButton.java (renamed from app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java)0
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/views/VpnStateImage.java99
-rw-r--r--app/src/main/res/drawable-hdpi/ic_btn_cancel.pngbin0 -> 486 bytes
-rw-r--r--app/src/main/res/drawable-hdpi/ic_btn_on.pngbin0 -> 1091 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_btn_cancel.pngbin0 -> 617 bytes
-rw-r--r--app/src/main/res/drawable-xhdpi/ic_btn_on.pngbin0 -> 1460 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_btn_cancel.pngbin0 -> 1009 bytes
-rw-r--r--app/src/main/res/drawable-xxhdpi/ic_btn_on.pngbin0 -> 2211 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_btn_cancel.pngbin0 -> 1361 bytes
-rw-r--r--app/src/main/res/drawable-xxxhdpi/ic_btn_on.pngbin0 -> 3115 bytes
-rw-r--r--app/src/main/res/drawable/button_circle_cancel.xml5
-rw-r--r--app/src/main/res/drawable/button_circle_cancel_pressed.xml64
-rw-r--r--app/src/main/res/drawable/button_circle_cancel_released.xml62
-rw-r--r--app/src/main/res/drawable/button_circle_start.xml7
-rw-r--r--app/src/main/res/drawable/button_circle_start_pressed.xml47
-rw-r--r--app/src/main/res/drawable/button_circle_start_released.xml46
-rw-r--r--app/src/main/res/drawable/button_circle_stop.xml5
-rw-r--r--app/src/main/res/drawable/button_circle_stop_pressed.xml47
-rw-r--r--app/src/main/res/drawable/button_circle_stop_released.xml46
-rw-r--r--app/src/main/res/drawable/ic_btn_cancel.pngbin0 -> 338 bytes
-rw-r--r--app/src/main/res/drawable/ic_btn_on.pngbin0 -> 777 bytes
-rw-r--r--app/src/main/res/values/colors.xml7
-rw-r--r--app/src/normal/java/se/leap/bitmaskclient/base/views/MainButton.java64
-rw-r--r--app/src/normal/res/drawable/rainbow_circle.xml103
-rw-r--r--app/src/normal/res/layout/v_main_button.xml (renamed from app/src/main/res/layout/v_main_button.xml)42
26 files changed, 1213 insertions, 117 deletions
diff --git a/app/src/custom/java/se/leap/bitmaskclient/base/fragments/EipFragment.java b/app/src/custom/java/se/leap/bitmaskclient/base/fragments/EipFragment.java
new file mode 100644
index 00000000..2588f436
--- /dev/null
+++ b/app/src/custom/java/se/leap/bitmaskclient/base/fragments/EipFragment.java
@@ -0,0 +1,686 @@
+/**
+ * Copyright (c) 2018 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient.base.fragments;
+
+import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_NONETWORK;
+import static se.leap.bitmaskclient.R.string.vpn_certificate_user_message;
+import static se.leap.bitmaskclient.base.models.Constants.ASK_TO_CANCEL_VPN;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_EARLY_ROUTES;
+import static se.leap.bitmaskclient.base.models.Constants.EIP_RESTART_ON_BOOT;
+import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
+import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_CONFIGURE_LEAP;
+import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_LOG_IN;
+import static se.leap.bitmaskclient.base.models.Constants.REQUEST_CODE_SWITCH_PROVIDER;
+import static se.leap.bitmaskclient.base.models.Constants.SHARED_PREFERENCES;
+import static se.leap.bitmaskclient.base.utils.ConfigHelper.isDefaultBitmask;
+import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getPreferredCity;
+import static se.leap.bitmaskclient.base.utils.ViewHelper.convertDimensionToPx;
+import static se.leap.bitmaskclient.eip.EipSetupObserver.gatewayOrder;
+import static se.leap.bitmaskclient.eip.EipSetupObserver.reconnectingWithDifferentGateway;
+import static se.leap.bitmaskclient.eip.GatewaysManager.Load.UNKNOWN;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.DOWNLOAD_GEOIP_JSON;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.USER_MESSAGE;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.graphics.ColorMatrix;
+import android.graphics.ColorMatrixColorFilter;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Vibrator;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.RelativeSizeSpan;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.widget.AppCompatImageView;
+import androidx.appcompat.widget.AppCompatTextView;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentTransaction;
+
+import java.util.Observable;
+import java.util.Observer;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+import butterknife.Unbinder;
+import de.blinkt.openvpn.core.ConnectionStatus;
+import de.blinkt.openvpn.core.IOpenVPNServiceInternal;
+import de.blinkt.openvpn.core.OpenVPNService;
+import de.blinkt.openvpn.core.VpnStatus;
+import de.blinkt.openvpn.core.connection.Connection;
+import se.leap.bitmaskclient.BuildConfig;
+import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.base.FragmentManagerEnhanced;
+import se.leap.bitmaskclient.base.MainActivity;
+import se.leap.bitmaskclient.base.fragments.DonationReminderDialog;
+import se.leap.bitmaskclient.base.fragments.GatewaySelectionFragment;
+import se.leap.bitmaskclient.base.models.Provider;
+import se.leap.bitmaskclient.base.models.ProviderObservable;
+import se.leap.bitmaskclient.base.utils.PreferenceHelper;
+import se.leap.bitmaskclient.base.views.LocationButton;
+import se.leap.bitmaskclient.base.views.MainButton;
+import se.leap.bitmaskclient.eip.EipCommand;
+import se.leap.bitmaskclient.eip.EipStatus;
+import se.leap.bitmaskclient.eip.GatewaysManager;
+import se.leap.bitmaskclient.providersetup.ProviderAPICommand;
+import se.leap.bitmaskclient.providersetup.ProviderListActivity;
+import se.leap.bitmaskclient.providersetup.activities.CustomProviderSetupActivity;
+import se.leap.bitmaskclient.providersetup.activities.LoginActivity;
+import se.leap.bitmaskclient.providersetup.models.LeapSRPSession;
+import se.leap.bitmaskclient.tor.TorServiceCommand;
+import se.leap.bitmaskclient.tor.TorStatusObservable;
+
+public class EipFragment extends Fragment implements Observer {
+
+ public final static String TAG = EipFragment.class.getSimpleName();
+
+
+ private SharedPreferences preferences;
+ private Provider provider;
+
+ @BindView(R.id.background)
+ AppCompatImageView background;
+
+ @BindView(R.id.main_button)
+ MainButton mainButton;
+
+ @BindView(R.id.gateway_location_button)
+ LocationButton locationButton;
+
+ @BindView(R.id.main_description)
+ AppCompatTextView mainDescription;
+
+ @BindView(R.id.sub_description)
+ AppCompatTextView subDescription;
+
+ private Unbinder unbinder;
+ private EipStatus eipStatus;
+ private ProviderObservable providerObservable;
+ private TorStatusObservable torStatusObservable;
+
+ private GatewaysManager gatewaysManager;
+
+ //---saved Instance -------
+ private final String KEY_SHOW_PENDING_START_CANCELLATION = "KEY_SHOW_PENDING_START_CANCELLATION";
+ private final String KEY_SHOW_ASK_TO_STOP_EIP = "KEY_SHOW_ASK_TO_STOP_EIP";
+ private boolean showPendingStartCancellation = false;
+ private boolean showAskToStopEip = false;
+ //------------------------
+ AlertDialog alertDialog;
+
+ private IOpenVPNServiceInternal mService;
+ // We use this service connection to detect if openvpn is running without network
+ private EipFragmentServiceConnection openVpnConnection;
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ Bundle arguments = getArguments();
+ Activity activity = getActivity();
+ if (activity != null) {
+ if (arguments != null) {
+ provider = arguments.getParcelable(PROVIDER_KEY);
+ if (provider == null) {
+ handleNoProvider(activity);
+ } else {
+ Log.d(TAG, provider.getName() + " configured as provider");
+ }
+ } else {
+ handleNoProvider(activity);
+ }
+ }
+ }
+
+ private void handleNoProvider(Activity activity) {
+ if (isDefaultBitmask()) {
+ activity.startActivityForResult(new Intent(activity, ProviderListActivity.class), REQUEST_CODE_SWITCH_PROVIDER);
+ } else {
+ Log.e(TAG, "no provider given - try to reconfigure custom provider");
+ startActivityForResult(new Intent(activity, CustomProviderSetupActivity.class), REQUEST_CODE_CONFIGURE_LEAP);
+
+ }
+
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ openVpnConnection = new EipFragmentServiceConnection();
+ eipStatus = EipStatus.getInstance();
+ providerObservable = ProviderObservable.getInstance();
+ torStatusObservable = TorStatusObservable.getInstance();
+ Activity activity = getActivity();
+ if (activity != null) {
+ preferences = getActivity().getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE);
+ } else {
+ Log.e(TAG, "activity is null in onCreate - no preferences set!");
+ }
+
+ gatewaysManager = new GatewaysManager(getContext());
+
+
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ eipStatus.addObserver(this);
+ torStatusObservable.addObserver(this);
+ providerObservable.addObserver(this);
+ View view = inflater.inflate(R.layout.f_eip, container, false);
+ unbinder = ButterKnife.bind(this, view);
+
+ try {
+ Bundle arguments = getArguments();
+ if (arguments != null && arguments.containsKey(ASK_TO_CANCEL_VPN) && arguments.getBoolean(ASK_TO_CANCEL_VPN)) {
+ arguments.remove(ASK_TO_CANCEL_VPN);
+ setArguments(arguments);
+ askToStopEIP();
+ }
+ } catch (IllegalStateException e) {
+ // probably setArguments failed because the fragments state is already saved
+ e.printStackTrace();
+ }
+
+ restoreFromSavedInstance(savedInstanceState);
+ locationButton.setOnClickListener(v -> {
+ FragmentManagerEnhanced fragmentManager = new FragmentManagerEnhanced(getActivity().getSupportFragmentManager());
+ Fragment fragment = new GatewaySelectionFragment();
+ fragmentManager.replace(R.id.main_container, fragment, MainActivity.TAG);
+ });
+ return view;
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (DonationReminderDialog.isCallable(getContext())) {
+ showDonationReminderDialog();
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (!eipStatus.isDisconnected()) {
+ openVpnConnection.bindService();
+ }
+ handleNewState();
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ openVpnConnection.unbindService();
+ }
+
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ if (showAskToStopEip) {
+ outState.putBoolean(KEY_SHOW_ASK_TO_STOP_EIP, true);
+ alertDialog.dismiss();
+ } else if (showPendingStartCancellation) {
+ outState.putBoolean(KEY_SHOW_PENDING_START_CANCELLATION, true);
+ alertDialog.dismiss();
+ }
+ }
+
+ private void restoreFromSavedInstance(Bundle savedInstanceState) {
+ if (savedInstanceState != null && savedInstanceState.containsKey(KEY_SHOW_PENDING_START_CANCELLATION)) {
+ showPendingStartCancellation = true;
+ askPendingStartCancellation();
+ } else if (savedInstanceState != null && savedInstanceState.containsKey(KEY_SHOW_ASK_TO_STOP_EIP)) {
+ showAskToStopEip = true;
+ askToStopEIP();
+ }
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ eipStatus.deleteObserver(this);
+ providerObservable.deleteObserver(this);
+ torStatusObservable.deleteObserver(this);
+ unbinder.unbind();
+ }
+
+ private void saveStatus(boolean restartOnBoot) {
+ preferences.edit().putBoolean(EIP_RESTART_ON_BOOT, restartOnBoot).apply();
+ }
+
+ @OnClick(R.id.main_button)
+ void onButtonClick() {
+ handleIcon();
+ }
+
+ void handleIcon() {
+ if (isOpenVpnRunningWithoutNetwork() || eipStatus.isConnected() || eipStatus.isConnecting() || eipStatus.isUpdatingVpnCert())
+ handleSwitchOff();
+ else
+ handleSwitchOn();
+ }
+
+ private void handleSwitchOn() {
+ Context context = getContext();
+ if (context == null) {
+ Log.e(TAG, "context is null when switch turning on");
+ return;
+ }
+
+ if (canStartEIP()) {
+ startEipFromScratch();
+ } else if (canLogInToStartEIP()) {
+ askUserToLogIn(getString(vpn_certificate_user_message));
+ } else {
+ // provider has no VpnCertificate but user is logged in
+ updateInvalidVpnCertificate();
+ }
+ }
+
+ private boolean canStartEIP() {
+ boolean certificateExists = provider.hasVpnCertificate();
+ boolean isAllowedAnon = provider.allowsAnonymous();
+ return (isAllowedAnon || certificateExists) && !eipStatus.isConnected() && !eipStatus.isConnecting();
+ }
+
+ private boolean canLogInToStartEIP() {
+ boolean isAllowedRegistered = provider.allowsRegistered();
+ boolean isLoggedIn = LeapSRPSession.loggedIn();
+ return isAllowedRegistered && !isLoggedIn && !eipStatus.isConnecting() && !eipStatus.isConnected();
+ }
+
+ private void handleSwitchOff() {
+ if (isOpenVpnRunningWithoutNetwork() || eipStatus.isConnecting() || eipStatus.isUpdatingVpnCert()) {
+ askPendingStartCancellation();
+ } else if (eipStatus.isConnected()) {
+ askToStopEIP();
+ }
+ }
+
+ private void setMainButtonEnabled(boolean enabled) {
+ locationButton.setEnabled(enabled);
+ mainButton.setEnabled(enabled);
+ }
+
+ public void startEipFromScratch() {
+ saveStatus(true);
+ Context context = getContext();
+ if (context == null) {
+ Log.e(TAG, "context is null when trying to start VPN");
+ return;
+ }
+ if (!provider.getGeoipUrl().isDefault() && provider.shouldUpdateGeoIpJson()) {
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(EIP_ACTION_START, true);
+ bundle.putBoolean(EIP_EARLY_ROUTES, false);
+ ProviderAPICommand.execute(context, DOWNLOAD_GEOIP_JSON, bundle, provider);
+ } else {
+ EipCommand.startVPN(context, false);
+ }
+ EipStatus.getInstance().updateState("UI_CONNECTING", "", 0, ConnectionStatus.LEVEL_START);
+ }
+
+ protected void stopEipIfPossible() {
+ Context context = getContext();
+ if (context == null) {
+ Log.e(TAG, "context is null when trying to stop EIP");
+ return;
+ }
+ EipCommand.stopVPN(context);
+ }
+
+ private void askPendingStartCancellation() {
+ Activity activity = getActivity();
+ if (activity == null) {
+ Log.e(TAG, "activity is null when asking to cancel");
+ return;
+ }
+
+ try {
+ AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity());
+ showPendingStartCancellation = true;
+ alertDialog = alertBuilder.setTitle(activity.getString(R.string.eip_cancel_connect_title))
+ .setMessage(activity.getString(R.string.eip_cancel_connect_text))
+ .setPositiveButton((android.R.string.yes), (dialog, which) -> {
+ Context context = getContext();
+ if (context != null && eipStatus.isUpdatingVpnCert() &&
+ TorStatusObservable.isRunning()) {
+ TorServiceCommand.stopTorServiceAsync(context.getApplicationContext());
+ }
+ stopEipIfPossible();
+ })
+ .setNegativeButton(activity.getString(android.R.string.no), (dialog, which) -> {
+ }).setOnDismissListener(dialog -> showPendingStartCancellation = false).show();
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ protected void askToStopEIP() {
+ Activity activity = getActivity();
+ if (activity == null) {
+ Log.e(TAG, "activity is null when asking to stop EIP");
+ return;
+ }
+ try {
+ AlertDialog.Builder alertBuilder = new AlertDialog.Builder(activity);
+ showAskToStopEip = true;
+ alertDialog = alertBuilder.setTitle(activity.getString(R.string.eip_cancel_connect_title))
+ .setMessage(activity.getString(R.string.eip_warning_browser_inconsistency))
+ .setPositiveButton((android.R.string.yes), (dialog, which) -> stopEipIfPossible())
+ .setNegativeButton(activity.getString(android.R.string.no), (dialog, which) -> {
+ }).setOnDismissListener(dialog -> showAskToStopEip = false).show();
+ } catch (IllegalStateException e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ @Override
+ public void update(Observable observable, Object data) {
+ if (observable instanceof EipStatus) {
+ eipStatus = (EipStatus) observable;
+ handleNewStateOnMain();
+
+ if (eipStatus.isConnecting()) {
+ openVpnConnection.bindService();
+ }
+ if ("NOPROCESS".equals(EipStatus.getInstance().getState())) {
+ //assure that the Service is shutdown completely if openvpn was stopped
+ openVpnConnection.unbindService();
+ }
+ } else if (observable instanceof ProviderObservable) {
+ provider = ((ProviderObservable) observable).getCurrentProvider();
+ } else if (observable instanceof TorStatusObservable && EipStatus.getInstance().isUpdatingVpnCert()) {
+ handleNewStateOnMain();
+ }
+ }
+
+ private void handleNewStateOnMain() {
+ Activity activity = getActivity();
+ if (activity != null) {
+ activity.runOnUiThread(this::handleNewState);
+ } else {
+ Log.e("EipFragment", "activity is null");
+ }
+ }
+
+ private void handleNewState() {
+ Activity activity = getActivity();
+ if (activity == null) {
+ Log.e(TAG, "activity is null while trying to handle new state");
+ return;
+ }
+
+ Log.d(TAG, "eip fragment eipStatus state: " + eipStatus.getState() + " - level: " + eipStatus.getLevel() + " - is reconnecting: " + eipStatus.isReconnecting());
+ if (eipStatus.isUpdatingVpnCert()) {
+ setMainButtonEnabled(true);
+ showConnectionTransitionLayout(true);
+ locationButton.setText(getString(R.string.eip_status_start_pending));
+ locationButton.setLocationLoad(UNKNOWN);
+ locationButton.showBridgeIndicator(false);
+ locationButton.showRecommendedIndicator(false);
+ mainDescription.setText(null);
+ String torStatus = TorStatusObservable.getStringForCurrentStatus(getContext());
+ if (!TextUtils.isEmpty(torStatus)) {
+ Spannable spannable = new SpannableString(torStatus);
+ spannable.setSpan(new RelativeSizeSpan(0.75f), 0, spannable.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ subDescription.setText(TextUtils.concat(getString(R.string.updating_certificate_message) + "\n", spannable));
+ } else {
+ subDescription.setText(getString(R.string.updating_certificate_message));
+ }
+ } else if (eipStatus.isConnecting()) {
+ setMainButtonEnabled(true);
+ showConnectionTransitionLayout(true);
+ locationButton.setText(getString(R.string.eip_status_start_pending));
+ locationButton.setLocationLoad(UNKNOWN);
+ locationButton.showBridgeIndicator(false);
+ locationButton.showRecommendedIndicator(false);
+ mainDescription.setText(null);
+ subDescription.setText(null);
+ } else if (eipStatus.isConnected()) {
+ setMainButtonEnabled(true);
+ mainButton.updateState(true, false, false);
+ Connection.TransportType transportType = PreferenceHelper.getUseBridges(getContext()) ? Connection.TransportType.OBFS4 : Connection.TransportType.OPENVPN;
+ locationButton.setLocationLoad(PreferenceHelper.useObfuscationPinning(getContext()) ? GatewaysManager.Load.UNKNOWN : gatewaysManager.getLoadForLocation(VpnStatus.getLastConnectedVpnName(), transportType));
+ locationButton.setText(VpnStatus.getLastConnectedVpnName());
+ locationButton.showBridgeIndicator(VpnStatus.isUsingBridges());
+ locationButton.showRecommendedIndicator(getPreferredCity(getContext())== null);
+ mainDescription.setText(R.string.eip_state_connected);
+ subDescription.setText(null);
+ colorBackground();
+ } else if(isOpenVpnRunningWithoutNetwork()) {
+ Log.d(TAG, "eip fragment eipStatus - isOpenVpnRunningWithoutNetwork");
+ setMainButtonEnabled(true);
+ mainButton.updateState(true, false, true);
+ locationButton.setText(VpnStatus.getCurrentlyConnectingVpnName());
+ locationButton.showBridgeIndicator(VpnStatus.isUsingBridges());
+ locationButton.showBridgeIndicator(VpnStatus.isUsingBridges());
+ locationButton.showRecommendedIndicator(getPreferredCity(getContext())== null);
+ colorBackgroundALittle();
+ mainDescription.setText(R.string.eip_state_connected);
+ subDescription.setText(R.string.eip_state_no_network);
+ } else if (eipStatus.isDisconnected() && reconnectingWithDifferentGateway()) {
+ showConnectionTransitionLayout(true);
+ // showRetryToast(activity);
+ locationButton.setText(getString(R.string.eip_status_start_pending));
+ locationButton.setLocationLoad(UNKNOWN);
+ locationButton.showBridgeIndicator(false);
+ locationButton.showRecommendedIndicator(false);
+ mainDescription.setText(null);
+ subDescription.setText(R.string.reconnecting);
+ } else if (eipStatus.isDisconnecting()) {
+ setMainButtonEnabled(false);
+ showConnectionTransitionLayout(false);
+ mainDescription.setText(R.string.eip_state_insecure);
+ } else if (eipStatus.isBlocking()) {
+ setMainButtonEnabled(true);
+ mainButton.updateState(true, false, true);
+ colorBackgroundALittle();
+ locationButton.setText(getString(R.string.no_location));
+ locationButton.setLocationLoad(UNKNOWN);
+ locationButton.showBridgeIndicator(false);
+ locationButton.showRecommendedIndicator(false);
+ mainDescription.setText(R.string.eip_state_connected);
+ subDescription.setText(getString(R.string.eip_state_blocking, getString(R.string.app_name)));
+ } else {
+ locationButton.setText(activity.getString(R.string.vpn_button_turn_on));
+ setMainButtonEnabled(true);
+ mainButton.updateState(false, false, false);
+ greyscaleBackground();
+ locationButton.setLocationLoad(UNKNOWN);
+ locationButton.showBridgeIndicator(false);
+ String city = getPreferredCity(getContext());
+ locationButton.setText(city == null ? getString(R.string.gateway_selection_recommended_location) : city);
+ locationButton.showRecommendedIndicator(false);
+ mainDescription.setText(R.string.eip_state_insecure);
+ subDescription.setText(R.string.connection_not_connected);
+ }
+ }
+
+ private void showToast(Activity activity, String message, boolean vibrateLong) {
+ LayoutInflater inflater = getLayoutInflater();
+ View layout = inflater.inflate(R.layout.custom_toast,
+ activity.findViewById(R.id.custom_toast_container));
+
+ AppCompatTextView text = layout.findViewById(R.id.text);
+ text.setText(message);
+
+ Vibrator v = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE);
+ if (vibrateLong) {
+ v.vibrate(100);
+ v.vibrate(200);
+ } else {
+ v.vibrate(100);
+ }
+
+ Toast toast = new Toast(activity.getApplicationContext());
+ toast.setGravity(Gravity.BOTTOM, 0, convertDimensionToPx(this.getContext(), R.dimen.stdpadding));
+ toast.setDuration(Toast.LENGTH_LONG);
+ toast.setView(layout);
+ toast.show();
+ }
+
+ private void showRetryToast(Activity activity) {
+ int nClosestGateway = gatewayOrder();
+ String message = String.format("Server number " + nClosestGateway + " not reachable. Trying next gateway.");
+ showToast(activity, message, true );
+ }
+
+ private void showConnectionTransitionLayout(boolean isConnecting) {
+ mainButton.updateState(true, isConnecting, false);
+ if (isConnecting) {
+ colorBackgroundALittle();
+ } else {
+ greyscaleBackground();
+ }
+ }
+
+ private boolean isOpenVpnRunningWithoutNetwork() {
+ boolean isRunning = false;
+ try {
+ isRunning = eipStatus.getLevel() == LEVEL_NONETWORK &&
+ mService.isVpnRunning();
+ } catch (Exception e) {
+ //eat me
+ e.printStackTrace();
+ }
+
+ return isRunning;
+ }
+
+ private void greyscaleBackground() {
+ if (BuildConfig.use_color_filter) {
+ ColorMatrix matrix = new ColorMatrix();
+ matrix.setSaturation(0);
+ ColorMatrixColorFilter cf = new ColorMatrixColorFilter(matrix);
+ background.setColorFilter(cf);
+ background.setImageAlpha(255);
+ }
+ }
+
+ private void colorBackgroundALittle() {
+ if (BuildConfig.use_color_filter) {
+ background.setColorFilter(null);
+ background.setImageAlpha(144);
+ }
+ }
+
+ private void colorBackground() {
+ if (BuildConfig.use_color_filter) {
+ background.setColorFilter(null);
+ background.setImageAlpha(210);
+ }
+ }
+
+ private void updateInvalidVpnCertificate() {
+ eipStatus.setUpdatingVpnCert(true);
+ ProviderAPICommand.execute(getContext(), UPDATE_INVALID_VPN_CERTIFICATE, provider);
+ }
+
+ private void askUserToLogIn(String userMessage) {
+ Intent intent = new Intent(getContext(), LoginActivity.class);
+ intent.putExtra(PROVIDER_KEY, provider);
+
+ if(userMessage != null) {
+ intent.putExtra(USER_MESSAGE, userMessage);
+ }
+
+ Activity activity = getActivity();
+ if (activity != null) {
+ activity.startActivityForResult(intent, REQUEST_CODE_LOG_IN);
+ }
+ }
+
+ private class EipFragmentServiceConnection implements ServiceConnection {
+ private final AtomicBoolean bind = new AtomicBoolean(false);
+
+ void bindService() {
+ Activity activity = getActivity();
+ if (activity == null) {
+ Log.e(TAG, "activity is null when binding OpenVpn");
+ return;
+ }
+ if (!bind.get()) {
+ activity.runOnUiThread(() -> {
+ Intent intent = new Intent(activity, OpenVPNService.class);
+ intent.setAction(OpenVPNService.START_SERVICE);
+
+ activity.bindService(intent, EipFragmentServiceConnection.this, Context.BIND_AUTO_CREATE);
+ bind.set(true);
+ });
+ }
+ }
+
+ void unbindService() {
+ Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+ if (bind.get()) {
+ activity.runOnUiThread(() -> {
+ activity.unbindService(EipFragmentServiceConnection.this);
+ bind.set(false);
+ });
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName className,
+ IBinder service) {
+ mService = IOpenVPNServiceInternal.Stub.asInterface(service);
+ handleNewState();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {
+ mService = null;
+ }
+ }
+
+ public void showDonationReminderDialog() {
+ try {
+ FragmentTransaction fragmentTransaction = new FragmentManagerEnhanced(
+ getActivity().getSupportFragmentManager()).removePreviousFragment(
+ DonationReminderDialog.TAG);
+ DialogFragment newFragment = new DonationReminderDialog();
+ newFragment.setCancelable(false);
+ newFragment.show(fragmentTransaction, DonationReminderDialog.TAG);
+ } catch (IllegalStateException | NullPointerException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java b/app/src/custom/java/se/leap/bitmaskclient/base/views/MainButton.java
index c5ac4544..c5ac4544 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/views/MainButton.java
+++ b/app/src/custom/java/se/leap/bitmaskclient/base/views/MainButton.java
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/views/VpnStateImage.java b/app/src/main/java/se/leap/bitmaskclient/base/views/VpnStateImage.java
deleted file mode 100644
index 2f8a4448..00000000
--- a/app/src/main/java/se/leap/bitmaskclient/base/views/VpnStateImage.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/**
- * Copyright (c) 2018 LEAP Encryption Access Project and contributers
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package se.leap.bitmaskclient.base.views;
-
-import android.content.Context;
-import androidx.constraintlayout.widget.ConstraintLayout;
-import androidx.appcompat.widget.AppCompatImageView;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.animation.AlphaAnimation;
-import android.view.animation.Animation;
-import android.widget.ProgressBar;
-
-import se.leap.bitmaskclient.R;
-
-/**
- * Created by cyberta on 12.02.18.
- */
-
-
-public class VpnStateImage extends ConstraintLayout {
-
- ProgressBar progressBar;
- AppCompatImageView stateIcon;
-
- public VpnStateImage(Context context) {
- super(context);
- initLayout(context);
- }
-
- public VpnStateImage(Context context, AttributeSet attrs) {
- super(context, attrs);
- initLayout(context);
- }
-
- public VpnStateImage(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- initLayout(context);
- }
-
- void initLayout(Context context) {
- LayoutInflater inflater = (LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- View rootview = inflater.inflate(R.layout.v_main_button, this, true);
- stateIcon = rootview.findViewById(R.id.vpn_state_key);
- progressBar = rootview.findViewById(R.id.progressBar);
- progressBar.setIndeterminate(true);
- }
-
- public void showProgress() {
- progressBar.setVisibility(VISIBLE);
- }
-
-
- public void stopProgress(boolean animated) {
- if (!animated) {
- progressBar.setVisibility(GONE);
- return;
- }
-
- AlphaAnimation fadeOutAnimation = new AlphaAnimation(1.0f, 0.0f);
- fadeOutAnimation.setDuration(1000);
- fadeOutAnimation.setAnimationListener(new Animation.AnimationListener() {
- @Override
- public void onAnimationStart(Animation animation) {}
-
- @Override
- public void onAnimationEnd(Animation animation) {
- progressBar.setVisibility(GONE);
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) {}
- });
-
- progressBar.startAnimation(fadeOutAnimation);
- }
-
- public void setStateIcon(int resource) {
- stateIcon.setImageResource(resource);
- }
-
-
-}
diff --git a/app/src/main/res/drawable-hdpi/ic_btn_cancel.png b/app/src/main/res/drawable-hdpi/ic_btn_cancel.png
new file mode 100644
index 00000000..00feedcd
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_btn_cancel.png
Binary files differ
diff --git a/app/src/main/res/drawable-hdpi/ic_btn_on.png b/app/src/main/res/drawable-hdpi/ic_btn_on.png
new file mode 100644
index 00000000..2b37d25e
--- /dev/null
+++ b/app/src/main/res/drawable-hdpi/ic_btn_on.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_btn_cancel.png b/app/src/main/res/drawable-xhdpi/ic_btn_cancel.png
new file mode 100644
index 00000000..d623f8f5
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_btn_cancel.png
Binary files differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_btn_on.png b/app/src/main/res/drawable-xhdpi/ic_btn_on.png
new file mode 100644
index 00000000..4fdc9464
--- /dev/null
+++ b/app/src/main/res/drawable-xhdpi/ic_btn_on.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_btn_cancel.png b/app/src/main/res/drawable-xxhdpi/ic_btn_cancel.png
new file mode 100644
index 00000000..af604d9b
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_btn_cancel.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_btn_on.png b/app/src/main/res/drawable-xxhdpi/ic_btn_on.png
new file mode 100644
index 00000000..d6cfc10b
--- /dev/null
+++ b/app/src/main/res/drawable-xxhdpi/ic_btn_on.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_btn_cancel.png b/app/src/main/res/drawable-xxxhdpi/ic_btn_cancel.png
new file mode 100644
index 00000000..6c17bd78
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_btn_cancel.png
Binary files differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_btn_on.png b/app/src/main/res/drawable-xxxhdpi/ic_btn_on.png
new file mode 100644
index 00000000..b0e54b8c
--- /dev/null
+++ b/app/src/main/res/drawable-xxxhdpi/ic_btn_on.png
Binary files differ
diff --git a/app/src/main/res/drawable/button_circle_cancel.xml b/app/src/main/res/drawable/button_circle_cancel.xml
new file mode 100644
index 00000000..1d94abca
--- /dev/null
+++ b/app/src/main/res/drawable/button_circle_cancel.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/button_circle_cancel_pressed" android:state_pressed="true"/>
+ <item android:drawable="@drawable/button_circle_cancel_released"/>
+</selector> \ No newline at end of file
diff --git a/app/src/main/res/drawable/button_circle_cancel_pressed.xml b/app/src/main/res/drawable/button_circle_cancel_pressed.xml
new file mode 100644
index 00000000..13970e9f
--- /dev/null
+++ b/app/src/main/res/drawable/button_circle_cancel_pressed.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- <item>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/white"/>
+ <size android:width="250dp" android:height="250dp"/>
+ </shape>
+ </item> -->
+ <item
+ android:left="-23dp"
+ android:right="-23dp"
+ android:top="-8dp"
+ android:bottom="-8dp"
+ >
+ <animated-rotate
+ android:drawable="@drawable/rainbow_circle"
+ android:pivotX="50.0%"
+ android:pivotY="50.0%"
+ android:fromDegrees="0.0"
+ android:toDegrees="360.0"
+ >
+ </animated-rotate>
+ </item>
+ <item
+ android:top="18dp"
+ android:bottom="12dp"
+ >
+ <shape android:shape="oval">
+ <solid android:color="@color/btn_yellow_dark"/>
+ <size android:width="250dp" android:height="250dp" />
+ </shape>
+ </item>
+ <item
+ android:top="18dp"
+ android:bottom="12dp"
+ >
+ <shape
+ android:shape="ring"
+ android:innerRadius="125dp"
+ android:useLevel="false"
+ android:thickness="3dp">
+ <gradient
+ android:type="radial"
+ android:gradientRadius="125dp"
+ android:centerX="0.50"
+ android:centerY="0.54"
+ android:startColor="#000000"
+ android:centerColor="#000000"
+ android:endColor="@color/transparent" />
+
+ </shape>
+ </item>
+ <item
+ android:top="68dp"
+ android:bottom="62dp"
+ android:left="50dp"
+ android:right="50dp"
+ >
+ <bitmap android:src="@drawable/ic_btn_cancel"
+ />
+ </item>
+
+
+</layer-list> \ No newline at end of file
diff --git a/app/src/main/res/drawable/button_circle_cancel_released.xml b/app/src/main/res/drawable/button_circle_cancel_released.xml
new file mode 100644
index 00000000..2e7bc4b7
--- /dev/null
+++ b/app/src/main/res/drawable/button_circle_cancel_released.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- <item>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/white"/>
+ <size android:width="250dp" android:height="250dp"/>
+ </shape>
+ </item>-->
+
+ <item
+ android:left="-23dp"
+ android:right="-23dp"
+ android:top="-8dp"
+ android:bottom="-8dp"
+ >
+ <animated-rotate
+ android:drawable="@drawable/rainbow_circle"
+ android:pivotX="50.0%"
+ android:pivotY="50.0%"
+ android:fromDegrees="0.0"
+ android:toDegrees="360.0"
+ >
+ </animated-rotate>
+ </item>
+ <item
+ android:bottom="15dp"
+ android:top="15dp"
+ >
+ <shape android:shape="oval">
+ <solid android:color="@color/btn_yellow"/>
+ <size android:width="250dp" android:height="250dp" />
+ </shape>
+ </item>
+ <item
+ android:bottom="15dp"
+ android:top="15dp"
+ >
+ <shape
+ android:shape="ring"
+ android:innerRadius="125dp"
+ android:useLevel="false"
+ android:thickness="10dp">
+ <gradient
+ android:type="radial"
+ android:gradientRadius="125dp"
+ android:centerX="0.51"
+ android:centerY="0.54"
+ android:startColor="#000000"
+ android:centerColor="#000000"
+ android:endColor="@color/transparent" />
+
+ </shape>
+ </item>
+ <item
+ android:top="65dp"
+ android:bottom="65dp"
+ android:left="50dp"
+ android:right="50dp"
+ >
+ <bitmap android:src="@drawable/ic_btn_cancel" />
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/app/src/main/res/drawable/button_circle_start.xml b/app/src/main/res/drawable/button_circle_start.xml
new file mode 100644
index 00000000..6d8482f4
--- /dev/null
+++ b/app/src/main/res/drawable/button_circle_start.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/button_circle_start_pressed" android:state_pressed="true">
+ </item>
+ <item android:drawable="@drawable/button_circle_start_released">
+ </item>
+</selector> \ No newline at end of file
diff --git a/app/src/main/res/drawable/button_circle_start_pressed.xml b/app/src/main/res/drawable/button_circle_start_pressed.xml
new file mode 100644
index 00000000..ce8eb8e1
--- /dev/null
+++ b/app/src/main/res/drawable/button_circle_start_pressed.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <!--<item>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/white"/>
+ <size android:width="250dp" android:height="250dp"/>
+ </shape>
+ </item>-->
+ <item
+ android:top="18dp"
+ android:bottom="12dp"
+ >
+ <shape android:shape="oval">
+ <solid android:color="@color/btn_red_dark"/>
+ <size android:width="250dp" android:height="250dp" />
+ </shape>
+ </item>
+ <item
+ android:top="18dp"
+ android:bottom="12dp"
+ >
+ <shape
+ android:shape="ring"
+ android:innerRadius="125dp"
+ android:useLevel="false"
+ android:thickness="3dp">
+ <gradient
+ android:type="radial"
+ android:gradientRadius="125dp"
+ android:centerX="0.50"
+ android:centerY="0.54"
+ android:startColor="#000000"
+ android:centerColor="#000000"
+ android:endColor="@color/transparent" />
+
+ </shape>
+ </item>
+ <item
+ android:top="68dp"
+ android:bottom="62dp"
+ android:left="50dp"
+ android:right="50dp"
+ >
+ <bitmap android:src="@drawable/ic_btn_on"
+ />
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/app/src/main/res/drawable/button_circle_start_released.xml b/app/src/main/res/drawable/button_circle_start_released.xml
new file mode 100644
index 00000000..ce1ba997
--- /dev/null
+++ b/app/src/main/res/drawable/button_circle_start_released.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <!--<item>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/white"/>
+ <size android:width="250dp" android:height="250dp"/>
+ </shape>
+ </item>-->
+ <item
+ android:bottom="15dp"
+ android:top="15dp"
+ >
+ <shape android:shape="oval">
+ <solid android:color="@color/btn_red"/>
+ <size android:width="250dp" android:height="250dp" />
+ </shape>
+ </item>
+ <item
+ android:bottom="15dp"
+ android:top="15dp"
+ >
+ <shape
+ android:shape="ring"
+ android:innerRadius="125dp"
+ android:useLevel="false"
+ android:thickness="10dp">
+ <gradient
+ android:type="radial"
+ android:gradientRadius="125dp"
+ android:centerX="0.51"
+ android:centerY="0.54"
+ android:startColor="#000000"
+ android:centerColor="#000000"
+ android:endColor="@color/transparent" />
+
+ </shape>
+ </item>
+ <item
+ android:top="65dp"
+ android:bottom="65dp"
+ android:left="50dp"
+ android:right="50dp"
+ >
+ <bitmap android:src="@drawable/ic_btn_on" />
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/app/src/main/res/drawable/button_circle_stop.xml b/app/src/main/res/drawable/button_circle_stop.xml
new file mode 100644
index 00000000..674cbf15
--- /dev/null
+++ b/app/src/main/res/drawable/button_circle_stop.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/button_circle_stop_pressed" android:state_pressed="true"/>
+ <item android:drawable="@drawable/button_circle_stop_released"/>
+</selector> \ No newline at end of file
diff --git a/app/src/main/res/drawable/button_circle_stop_pressed.xml b/app/src/main/res/drawable/button_circle_stop_pressed.xml
new file mode 100644
index 00000000..0561455a
--- /dev/null
+++ b/app/src/main/res/drawable/button_circle_stop_pressed.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <!--<item>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/white"/>
+ <size android:width="250dp" android:height="250dp"/>
+ </shape>
+ </item>-->
+ <item
+ android:top="18dp"
+ android:bottom="12dp"
+ >
+ <shape android:shape="oval">
+ <solid android:color="@color/btn_green_dark"/>
+ <size android:width="250dp" android:height="250dp" />
+ </shape>
+ </item>
+ <item
+ android:top="18dp"
+ android:bottom="12dp"
+ >
+ <shape
+ android:shape="ring"
+ android:innerRadius="125dp"
+ android:useLevel="false"
+ android:thickness="3dp">
+ <gradient
+ android:type="radial"
+ android:gradientRadius="125dp"
+ android:centerX="0.51"
+ android:centerY="0.54"
+ android:startColor="#000000"
+ android:centerColor="#000000"
+ android:endColor="@color/transparent" />
+
+ </shape>
+ </item>
+ <item
+ android:top="68dp"
+ android:bottom="62dp"
+ android:left="50dp"
+ android:right="50dp"
+ >
+ <bitmap android:src="@drawable/ic_btn_cancel"
+ />
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/app/src/main/res/drawable/button_circle_stop_released.xml b/app/src/main/res/drawable/button_circle_stop_released.xml
new file mode 100644
index 00000000..2e168c2d
--- /dev/null
+++ b/app/src/main/res/drawable/button_circle_stop_released.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <!--<item>
+ <shape android:shape="rectangle">
+ <solid android:color="@color/white"/>
+ <size android:width="250dp" android:height="250dp"/>
+ </shape>
+ </item>-->
+ <item
+ android:bottom="15dp"
+ android:top="15dp"
+ >
+ <shape android:shape="oval">
+ <solid android:color="@color/btn_green"/>
+ <size android:width="250dp" android:height="250dp" />
+ </shape>
+ </item>
+ <item
+ android:bottom="15dp"
+ android:top="15dp"
+ >
+ <shape
+ android:shape="ring"
+ android:innerRadius="125dp"
+ android:useLevel="false"
+ android:thickness="10dp">
+ <gradient
+ android:type="radial"
+ android:gradientRadius="125dp"
+ android:centerX="0.51"
+ android:centerY="0.54"
+ android:startColor="#000000"
+ android:centerColor="#000000"
+ android:endColor="@color/transparent" />
+
+ </shape>
+ </item>
+ <item
+ android:top="65dp"
+ android:bottom="65dp"
+ android:left="50dp"
+ android:right="50dp"
+ >
+ <bitmap android:src="@drawable/ic_btn_cancel" />
+ </item>
+</layer-list> \ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_btn_cancel.png b/app/src/main/res/drawable/ic_btn_cancel.png
new file mode 100644
index 00000000..0b55460a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_btn_cancel.png
Binary files differ
diff --git a/app/src/main/res/drawable/ic_btn_on.png b/app/src/main/res/drawable/ic_btn_on.png
new file mode 100644
index 00000000..be160a33
--- /dev/null
+++ b/app/src/main/res/drawable/ic_btn_on.png
Binary files differ
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 40b51436..dbc7326f 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -14,6 +14,7 @@
<color name="black800_secondary">#3b3b3b</color>
<color name="black800_transparent">#AA424242</color>
<color name="black800_high_transparent">#22424242</color>
+ <color name="transparent">#00000000</color>
<color name="red200">#ef9a9a</color>
<color name="pink200">#f48fb1</color>
@@ -45,5 +46,11 @@
<color name="colorLocationButtonTint">@color/black800</color>
<color name="colorLocationButtonTintTransparent">@color/black800_high_transparent</color>
<color name="colorWarning">#B33A3A</color>
+ <color name="btn_red">#FF7D7D</color>
+ <color name="btn_red_dark">#c84c51</color>
+ <color name="btn_yellow">#FFBF00</color>
+ <color name="btn_yellow_dark">#C78F00</color>
+ <color name="btn_green">#9FC17F</color>
+ <color name="btn_green_dark">#709152</color>
</resources>
diff --git a/app/src/normal/java/se/leap/bitmaskclient/base/views/MainButton.java b/app/src/normal/java/se/leap/bitmaskclient/base/views/MainButton.java
new file mode 100644
index 00000000..d8766e6b
--- /dev/null
+++ b/app/src/normal/java/se/leap/bitmaskclient/base/views/MainButton.java
@@ -0,0 +1,64 @@
+package se.leap.bitmaskclient.base.views;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.widget.RelativeLayout;
+
+import androidx.appcompat.widget.AppCompatImageView;
+import androidx.core.content.ContextCompat;
+
+import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.databinding.VMainButtonBinding;
+
+public class MainButton extends RelativeLayout {
+
+ private static final String TAG = MainButton.class.getSimpleName();
+
+ AppCompatImageView button;
+ VMainButtonBinding binding;
+
+ private boolean isOn = false;
+ private boolean isProcessing = false;
+ private boolean isError = true;
+
+
+ public MainButton(Context context) {
+ super(context);
+ initLayout(context);
+ }
+
+ public MainButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ initLayout(context);
+ }
+
+ public MainButton(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ initLayout(context);
+ }
+
+
+ @TargetApi(21)
+ public MainButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ initLayout(context);
+ }
+
+ private void initLayout(Context context) {
+ binding = VMainButtonBinding.inflate(LayoutInflater.from(context), this, true);
+ button = binding.button;
+ }
+
+ public void updateState(boolean isOn, boolean isProcessing, boolean isError) {
+ if (isProcessing) {
+ button.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.button_circle_cancel));
+ } else {
+ button.setImageDrawable(
+ ContextCompat.getDrawable(getContext(),
+ isOn ? R.drawable.button_circle_stop : R.drawable.button_circle_start));
+ }
+ }
+
+}
diff --git a/app/src/normal/res/drawable/rainbow_circle.xml b/app/src/normal/res/drawable/rainbow_circle.xml
new file mode 100644
index 00000000..44a35ba9
--- /dev/null
+++ b/app/src/normal/res/drawable/rainbow_circle.xml
@@ -0,0 +1,103 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+ android:name="circle"
+ android:viewportWidth="91"
+ android:viewportHeight="91"
+ android:width="91dp"
+ android:height="91dp">
+ <path
+ android:pathData="M83.9459 61.7492C81.6735 67.1718 78.2509 72.0496 73.9091 76.0535L45.5808 46.0417L83.9459 61.7492Z"
+ android:fillColor="#FFCC80" />
+ <path
+ android:pathData="M74.9439 75.0657C70.7802 79.1812 65.7785 82.3744 60.2709 84.4334L45.5806 46.0421L74.9439 75.0657Z"
+ android:fillColor="#FFE082" />
+ <path
+ android:pathData="M61.4719 83.9632C55.9543 86.2222 50.0087 87.2783 44.0406 87.0594L45.5806 46.0421L61.4719 83.9632Z"
+ android:fillColor="#FFF59D" />
+ <path
+ android:pathData="M45.5803 87.088C39.516 87.088 33.5253 85.7751 28.0292 83.2417L45.5803 46.0425L45.5803 87.088Z"
+ android:fillColor="#E6EE9C" />
+ <path
+ android:pathData="M29.6888 83.9635C24.2224 81.7254 19.303 78.3586 15.2601 74.0885L45.5801 46.0424L29.6888 83.9635Z"
+ android:fillColor="#C5E1A5" />
+ <path
+ android:pathData="M16.2165 75.0658C11.9261 70.825 8.62802 65.7065 6.55433 60.0703L45.5798 46.0422L16.2165 75.0658Z"
+ android:fillColor="#A5D6A7" />
+ <path
+ android:pathData="M7.21455 61.7497C4.89145 56.2061 3.82625 50.2265 4.09398 44.232L45.5796 46.0422L7.21455 61.7497Z"
+ android:fillColor="#80CBC4" />
+ <path
+ android:pathData="M4.05357 46.0421C4.05357 40.2602 5.28944 34.5435 7.68023 29.2665L45.5796 46.0421L4.05357 46.0421Z"
+ android:fillColor="#80DEEA" />
+ <path
+ android:pathData="M7.21454 30.3342C9.56893 24.716 13.1569 19.685 17.7191 15.6051L45.5796 46.0416L7.21454 30.3342Z"
+ android:fillColor="#81D4FA" />
+ <path
+ android:pathData="M16.216 17.0181C20.5162 12.7677 25.7084 9.50267 31.4256 7.45389L45.5793 46.0416L16.216 17.0181Z"
+ android:fillColor="#90CAF9" />
+ <path
+ android:pathData="M29.6883 8.12016C35.3597 5.79816 41.4814 4.7479 47.6127 5.04498L45.5796 46.0413L29.6883 8.12016Z"
+ android:fillColor="#9FA8DA" />
+ <path
+ android:pathData="M45.5798 4.99576C51.3771 4.99576 57.1101 6.19555 62.4099 8.51791L45.5798 46.0413L45.5798 4.99576Z"
+ android:fillColor="#B39DDB" />
+ <path
+ android:pathData="M61.4714 8.11991C66.8116 10.3063 71.6312 13.5706 75.6218 17.7038L45.5801 46.041L61.4714 8.11991Z"
+ android:fillColor="#CE93D8" />
+ <path
+ android:pathData="M74.9437 17.0175C79.0466 21.0729 82.2441 25.9333 84.3301 31.2851L45.5803 46.041L74.9437 17.0175Z"
+ android:fillColor="#F48FB1" />
+ <path
+ android:pathData="M83.9456 30.3337C86.2094 35.7356 87.2793 41.5533 87.0839 47.3971L45.5806 46.0411L83.9456 30.3337Z"
+ android:fillColor="#EF9A9A" />
+ <path
+ android:pathData="M87.1066 46.0416C87.1066 51.4632 86.02 56.831 83.9094 61.8351L45.5806 46.0416L87.1066 46.0416Z"
+ android:fillColor="#FFAB91" />
+ <path
+ android:pathData="M83.8543 60.835C81.5944 66.2576 78.1907 71.1354 73.8728 75.1393L45.7004 45.1276L83.8543 60.835Z"
+ android:fillColor="#FFCC80" />
+ <path
+ android:pathData="M74.9019 74.1516C70.7611 78.2671 65.787 81.4604 60.3097 83.5193L45.7002 45.1281L74.9019 74.1516Z"
+ android:fillColor="#FFE082" />
+ <path
+ android:pathData="M61.5041 83.0491C56.0168 85.3082 50.1039 86.3642 44.1687 86.1453L45.7002 45.1281L61.5041 83.0491Z"
+ android:fillColor="#FFF59D" />
+ <path
+ android:pathData="M45.7002 86.1734C39.6693 86.1734 33.7115 84.8606 28.2457 82.3272L45.7002 45.1279L45.7002 86.1734Z"
+ android:fillColor="#E6EE9C" />
+ <path
+ android:pathData="M29.8961 83.049C24.4598 80.811 19.5675 77.4442 15.5469 73.1741L45.7 45.1279L29.8961 83.049Z"
+ android:fillColor="#C5E1A5" />
+ <path
+ android:pathData="M16.4977 74.1517C12.231 69.911 8.95103 64.7924 6.88875 59.1563L45.6995 45.1282L16.4977 74.1517Z"
+ android:fillColor="#A5D6A7" />
+ <path
+ android:pathData="M7.54558 60.8351C5.23527 55.2915 4.17593 49.3119 4.44219 43.3175L45.6995 45.1277L7.54558 60.8351Z"
+ android:fillColor="#80CBC4" />
+ <path
+ android:pathData="M4.40175 45.1277C4.40175 39.3457 5.63082 33.6291 8.00845 28.3521L45.6992 45.1277L4.40175 45.1277Z"
+ android:fillColor="#80DEEA" />
+ <path
+ android:pathData="M7.54533 29.4201C9.88677 23.8019 13.455 18.7709 17.992 14.691L45.6992 45.1276L7.54533 29.4201Z"
+ android:fillColor="#81D4FA" />
+ <path
+ android:pathData="M16.4972 16.104C20.7738 11.8536 25.9374 8.5886 31.6231 6.53981L45.699 45.1276L16.4972 16.104Z"
+ android:fillColor="#90CAF9" />
+ <path
+ android:pathData="M29.8956 7.20611C35.5358 4.88411 41.6238 3.83384 47.7214 4.13092L45.6995 45.1272L29.8956 7.20611Z"
+ android:fillColor="#9FA8DA" />
+ <path
+ android:pathData="M45.6997 4.08133C51.465 4.08133 57.1665 5.28113 62.4371 7.60349L45.6997 45.1268L45.6997 4.08133Z"
+ android:fillColor="#B39DDB" />
+ <path
+ android:pathData="M61.5036 7.20587C66.8144 9.39225 71.6074 12.6565 75.576 16.7898L45.6997 45.127L61.5036 7.20587Z"
+ android:fillColor="#CE93D8" />
+ <path
+ android:pathData="M74.9017 16.1034C78.982 20.1589 82.162 25.0192 84.2365 30.3711L45.7 45.127L74.9017 16.1034Z"
+ android:fillColor="#F48FB1" />
+ <path
+ android:pathData="M83.8541 29.4196C86.1054 34.8216 87.1695 40.6392 86.9751 46.4831L45.7002 45.1271L83.8541 29.4196Z"
+ android:fillColor="#EF9A9A" />
+ <path
+ android:pathData="M86.9979 45.1272C86.9979 50.5487 85.9172 55.9166 83.8183 60.9207L45.7004 45.1272L86.9979 45.1272Z"
+ android:fillColor="#FFAB91" />
+</vector> \ No newline at end of file
diff --git a/app/src/main/res/layout/v_main_button.xml b/app/src/normal/res/layout/v_main_button.xml
index 741fc88f..e3f96693 100644
--- a/app/src/main/res/layout/v_main_button.xml
+++ b/app/src/normal/res/layout/v_main_button.xml
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- xmlns:app="http://schemas.android.com/apk/res-auto">
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.Guideline
@@ -92,7 +92,7 @@
app:layout_constraintGuide_percent="0.975" />
- <ProgressBar
+ <!-- <ProgressBar
android:id="@+id/progressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
@@ -108,10 +108,23 @@
android:indeterminateDrawable="@drawable/progressbar_circle"
android:interpolator="@android:anim/decelerate_interpolator"
android:indeterminateBehavior="cycle"
- />
+ />-->
+
+ <!--<androidx.appcompat.widget.AppCompatImageView
+ android:id="@+id/progress_circular"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:scaleType="fitXY"
+
+ app:layout_constraintTop_toTopOf="@+id/button"
+ app:layout_constraintBottom_toBottomOf="@+id/button"
+ app:layout_constraintLeft_toLeftOf="@+id/button"
+ app:layout_constraintRight_toRightOf="@+id/button"
+ app:layout_constraintDimensionRatio="1:1"
+ android:src="@drawable/rainbow_circle" />-->
<androidx.appcompat.widget.AppCompatImageView
- android:id="@+id/circle"
+ android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/vpn_btn_guideline_bottom"
@@ -119,18 +132,11 @@
app:layout_constraintStart_toStartOf="@+id/vpn_btn_guideline_left"
app:layout_constraintTop_toTopOf="@+id/vpn_btn_guideline_top"
app:layout_constraintDimensionRatio="1:1"
- app:srcCompat="@drawable/black_circle" />
+ app:srcCompat="@drawable/button_circle_start"
+ />
+
+
+
- <androidx.appcompat.widget.AppCompatImageView
- android:id="@+id/vpn_state_key"
- android:layout_width="0dp"
- android:layout_height="0dp"
- app:layout_constraintBottom_toTopOf="@+id/icn_guideline_bottom"
- app:layout_constraintEnd_toStartOf="@+id/icn_guideline_right"
- app:layout_constraintStart_toStartOf="@+id/icn_guideline_left"
- app:layout_constraintTop_toTopOf="@+id/icn_guideline_top"
- app:layout_constraintDimensionRatio="1:1"
- app:layout_constraintVertical_bias="0.35"
- app:srcCompat="@drawable/ic_btn_on_disabled" />
</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file