diff options
15 files changed, 145 insertions, 41 deletions
diff --git a/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java b/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java index eeed29bc..02184f03 100644 --- a/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java +++ b/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java @@ -17,6 +17,7 @@ import android.preference.PreferenceManager; import se.leap.bitmaskclient.R; import de.blinkt.openvpn.core.VpnStatus.ByteCountListener; +import se.leap.bitmaskclient.tethering.TetheringObservable; import java.util.LinkedList; import java.util.Objects; @@ -143,8 +144,8 @@ public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountL networkStateChange(context); } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { boolean screenOffPause = prefs.getBoolean("screenoff", false); - - if (screenOffPause) { + boolean isTethering = TetheringObservable.getInstance().getTetheringState().isVpnTetheringRunning(); + if (screenOffPause && !isTethering) { if (VpnStatus.getLastConnectedVpnProfile() != null && !VpnStatus.getLastConnectedVpnProfile().mPersistTun) VpnStatus.logError(R.string.screen_nopersistenttun); diff --git a/app/src/main/java/se/leap/bitmaskclient/Constants.java b/app/src/main/java/se/leap/bitmaskclient/Constants.java index 60edc941..6ec3076c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/Constants.java +++ b/app/src/main/java/se/leap/bitmaskclient/Constants.java @@ -15,7 +15,6 @@ public interface Constants { String LAST_USED_PROFILE = "last_used_profile"; String EXCLUDED_APPS = "excluded_apps"; String USE_PLUGGABLE_TRANSPORTS = "usePluggableTransports"; - String SU_PERMISSION = "su_permission"; String ALLOW_TETHERING_BLUETOOTH = "tethering_bluetooth"; String ALLOW_TETHERING_WIFI = "tethering_wifi"; String ALLOW_TETHERING_USB = "tethering_usb"; diff --git a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java index 104f1edc..715d278d 100644 --- a/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/drawer/NavigationDrawerFragment.java @@ -45,10 +45,11 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; +import java.util.Observable; +import java.util.Observer; import java.util.Set; import de.blinkt.openvpn.core.VpnStatus; -import se.leap.bitmaskclient.Constants; import se.leap.bitmaskclient.EipFragment; import se.leap.bitmaskclient.FragmentManagerEnhanced; import se.leap.bitmaskclient.MainActivity; @@ -57,12 +58,14 @@ import se.leap.bitmaskclient.ProviderListActivity; import se.leap.bitmaskclient.ProviderObservable; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.eip.EipCommand; +import se.leap.bitmaskclient.eip.EipStatus; import se.leap.bitmaskclient.firewall.FirewallManager; import se.leap.bitmaskclient.fragments.AboutFragment; import se.leap.bitmaskclient.fragments.AlwaysOnDialog; import se.leap.bitmaskclient.fragments.ExcludeAppsFragment; import se.leap.bitmaskclient.fragments.LogFragment; import se.leap.bitmaskclient.fragments.TetheringDialog; +import se.leap.bitmaskclient.tethering.TetheringObservable; import se.leap.bitmaskclient.utils.PreferenceHelper; import se.leap.bitmaskclient.views.IconSwitchEntry; import se.leap.bitmaskclient.views.IconTextEntry; @@ -76,6 +79,8 @@ import static se.leap.bitmaskclient.Constants.ENABLE_DONATION; import static se.leap.bitmaskclient.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.Constants.REQUEST_CODE_SWITCH_PROVIDER; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; +import static se.leap.bitmaskclient.Constants.USE_IPv6_FIREWALL; +import static se.leap.bitmaskclient.Constants.USE_PLUGGABLE_TRANSPORTS; import static se.leap.bitmaskclient.R.string.about_fragment_title; import static se.leap.bitmaskclient.R.string.exclude_apps_fragment_title; import static se.leap.bitmaskclient.R.string.log_fragment_title; @@ -92,7 +97,7 @@ import static se.leap.bitmaskclient.utils.PreferenceHelper.usePluggableTransport * 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 implements SharedPreferences.OnSharedPreferenceChangeListener { +public class NavigationDrawerFragment extends Fragment implements SharedPreferences.OnSharedPreferenceChangeListener, Observer { /** * Per the design guidelines, you should show the drawer on launch until the user manually @@ -126,6 +131,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen private final static String KEY_SHOW_SAVE_BATTERY_ALERT = "KEY_SHOW_SAVE_BATTERY_ALERT"; private volatile boolean showSaveBattery = false; AlertDialog alertDialog; + private FirewallManager firewallManager; @Override public void onCreate(Bundle savedInstanceState) { @@ -135,6 +141,8 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen preferences = getContext().getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE); userLearnedDrawer = preferences.getBoolean(PREF_USER_LEARNED_DRAWER, false); preferences.registerOnSharedPreferenceChangeListener(this); + firewallManager = new FirewallManager(getContext().getApplicationContext(), false); + } @Override @@ -149,9 +157,18 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen Bundle savedInstanceState) { drawerView = inflater.inflate(R.layout.f_drawer_main, container, false); restoreFromSavedInstance(savedInstanceState); + TetheringObservable.getInstance().addObserver(this); + EipStatus.getInstance().addObserver(this); return drawerView; } + @Override + public void onDestroyView() { + super.onDestroyView(); + TetheringObservable.getInstance().deleteObserver(this); + EipStatus.getInstance().deleteObserver(this); + } + public boolean isDrawerOpen() { return drawerLayout != null && drawerLayout.isDrawerOpen(fragmentContainerView); } @@ -302,6 +319,7 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen private void initSaveBatteryEntry() { saveBattery = drawerView.findViewById(R.id.battery_switch); + saveBattery.showSubtitle(false); saveBattery.setChecked(getSaveBattery(getContext())); saveBattery.setOnCheckedChangeListener(((buttonView, isChecked) -> { if (!buttonView.isPressed()) { @@ -313,6 +331,16 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen saveBattery(getContext(), false); } })); + boolean enableEntry = !TetheringObservable.getInstance().getTetheringState().isVpnTetheringRunning(); + enableSaveBatteryEntry(enableEntry); + } + + private void enableSaveBatteryEntry(boolean enabled) { + if (saveBattery.isEnabled() == enabled) { + return; + } + saveBattery.setEnabled(enabled); + saveBattery.showSubtitle(!enabled); } private void initAlwaysOnVpnEntry() { @@ -374,13 +402,12 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen firewall = drawerView.findViewById(R.id.enableIPv6Firewall); boolean show = showExperimentalFeatures(getContext()); firewall.setVisibility(show ? VISIBLE : GONE); - firewall.setChecked(PreferenceHelper.useIpv6Firewall(this.getContext())); + firewall.setChecked(PreferenceHelper.useIpv6Firewall(getContext())); firewall.setOnCheckedChangeListener((buttonView, isChecked) -> { if (!buttonView.isPressed()) { return; } PreferenceHelper.setUseIPv6Firewall(getContext(), isChecked); - FirewallManager firewallManager = new FirewallManager(getContext().getApplicationContext(), false); if (VpnStatus.isVPNActive()) { if (isChecked) { firewallManager.startIPv6Firewall(); @@ -626,8 +653,22 @@ public class NavigationDrawerFragment extends Fragment implements SharedPreferen @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - if (key.equals(Constants.USE_PLUGGABLE_TRANSPORTS)) { + if (key.equals(USE_PLUGGABLE_TRANSPORTS)) { initUseBridgesEntry(); + } else if (key.equals(USE_IPv6_FIREWALL)) { + initFirewallEntry(); + } + } + + @Override + public void update(Observable o, Object arg) { + if (o instanceof TetheringObservable || o instanceof EipStatus) { + try { + getActivity().runOnUiThread(() -> + enableSaveBatteryEntry(!TetheringObservable.getInstance().getTetheringState().isVpnTetheringRunning())); + } catch (NullPointerException npe) { + // eat me + } } } } diff --git a/app/src/main/java/se/leap/bitmaskclient/firewall/FirewallManager.java b/app/src/main/java/se/leap/bitmaskclient/firewall/FirewallManager.java index c148497b..ace8a298 100644 --- a/app/src/main/java/se/leap/bitmaskclient/firewall/FirewallManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/firewall/FirewallManager.java @@ -17,11 +17,15 @@ package se.leap.bitmaskclient.firewall; */ import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.widget.Toast; import java.util.Observable; import java.util.Observer; import de.blinkt.openvpn.core.VpnStatus; +import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.tethering.TetheringObservable; import se.leap.bitmaskclient.tethering.TetheringState; import se.leap.bitmaskclient.utils.PreferenceHelper; @@ -80,9 +84,18 @@ public class FirewallManager implements FirewallCallback, Observer { @Override public void onSuRequested(boolean success) { - PreferenceHelper.setSuPermission(context, success); if (!success) { VpnStatus.logError("[FIREWALL] Root permission needed to execute custom firewall rules."); + new Handler(Looper.getMainLooper()).post(() -> { + Toast.makeText(context.getApplicationContext(), context.getString(R.string.root_permission_error, context.getString(R.string.app_name)), Toast.LENGTH_LONG).show(); + }); + TetheringObservable.allowVpnWifiTethering(false); + TetheringObservable.allowVpnUsbTethering(false); + TetheringObservable.allowVpnBluetoothTethering(false); + PreferenceHelper.allowWifiTethering(context, false); + PreferenceHelper.allowUsbTethering(context, false); + PreferenceHelper.allowBluetoothTethering(context, false); + PreferenceHelper.setUseIPv6Firewall(context, false); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/firewall/SetupTetheringTask.java b/app/src/main/java/se/leap/bitmaskclient/firewall/SetupTetheringTask.java index 7abd01a8..edf79add 100644 --- a/app/src/main/java/se/leap/bitmaskclient/firewall/SetupTetheringTask.java +++ b/app/src/main/java/se/leap/bitmaskclient/firewall/SetupTetheringTask.java @@ -130,7 +130,7 @@ public class SetupTetheringTask extends AsyncTask<Void, Boolean, Boolean> { } private boolean removeWifiTetheringRules(TetheringState state, StringBuilder log) throws Exception { - Log.d(TAG, "add Wifi tethering Rules"); + Log.d(TAG, "remove Wifi tethering Rules"); String[] removeRules = getDeletionRules(state, state.lastSeenWifiAddress, state.lastSeenWifiInterface); return runBlockingCmd(removeRules, log) == 0; } @@ -142,20 +142,24 @@ public class SetupTetheringTask extends AsyncTask<Void, Boolean, Boolean> { } private boolean removeUsbTetheringRules(TetheringState state, StringBuilder log) throws Exception { - Log.d(TAG, "add usb tethering rules"); + Log.d(TAG, "remove usb tethering rules"); String[] addRules = getDeletionRules(state, state.lastSeenUsbAddress, state.lastSeenUsbInterface); return runBlockingCmd(addRules, log) == 0; } - //TODO: implement the follwing methods -v - private boolean removeBluetoothTetheringRules(TetheringState state, StringBuilder log) { - return true; + private boolean addBluetoothTetheringRules(TetheringState state, StringBuilder log) throws Exception { + Log.d(TAG, "add bluetooth tethering rules"); + String[] addRules = getAdditionRules(state.bluetoothAddress, state.bluetoothInterface); + return runBlockingCmd(addRules, log) == 0; } - private boolean addBluetoothTetheringRules(TetheringState state, StringBuilder log) { - return true; + private boolean removeBluetoothTetheringRules(TetheringState state, StringBuilder log) throws Exception { + Log.d(TAG, "remove bluetooth tethering rules"); + String[] addRules = getDeletionRules(state, state.lastSeenBluetoothAddress, state.lastSeenBluetoothInterface); + return runBlockingCmd(addRules, log) == 0; } + private String[] getAdditionRules(String addressRange, String interfaceName) { return new String[] { "su", diff --git a/app/src/main/java/se/leap/bitmaskclient/fragments/TetheringDialog.java b/app/src/main/java/se/leap/bitmaskclient/fragments/TetheringDialog.java index c53d2a6c..900d1885 100644 --- a/app/src/main/java/se/leap/bitmaskclient/fragments/TetheringDialog.java +++ b/app/src/main/java/se/leap/bitmaskclient/fragments/TetheringDialog.java @@ -1,6 +1,8 @@ package se.leap.bitmaskclient.fragments; import android.app.Dialog; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; import android.content.Intent; import android.graphics.drawable.Drawable; import android.os.Bundle; @@ -150,10 +152,10 @@ public class TetheringDialog extends AppCompatDialogFragment implements Observer .setPositiveButton(android.R.string.ok, (dialog, id) -> { PreferenceHelper.allowWifiTethering(getContext(), dataset[0].checked); PreferenceHelper.allowUsbTethering(getContext(), dataset[1].checked); -// PreferenceHelper.allowBluetoothTethering(getContext(), dataset[2].checked); + PreferenceHelper.allowBluetoothTethering(getContext(), dataset[2].checked); TetheringObservable.allowVpnWifiTethering(dataset[0].checked); TetheringObservable.allowVpnUsbTethering(dataset[1].checked); -// TetheringObservable.allowVpnBluetoothTethering(dataset[2].checked); + TetheringObservable.allowVpnBluetoothTethering(dataset[2].checked); FirewallManager firewallManager = new FirewallManager(getContext().getApplicationContext(), false); if (VpnStatus.isVPNActive()) { if (TetheringObservable.getInstance().getTetheringState().hasAnyDeviceTetheringEnabled() && @@ -172,7 +174,7 @@ public class TetheringDialog extends AppCompatDialogFragment implements Observer super.onResume(); dataset[0].enabled = TetheringObservable.getInstance().isWifiTetheringEnabled(); dataset[1].enabled = TetheringObservable.getInstance().isUsbTetheringEnabled(); -// dataset[2].enabled = TetheringObservable.getInstance().isBluetoothTetheringEnabled(); + dataset[2].enabled = TetheringObservable.getInstance().isBluetoothTetheringEnabled(); adapter.notifyDataSetChanged(); TetheringObservable.getInstance().addObserver(this); } @@ -199,8 +201,18 @@ public class TetheringDialog extends AppCompatDialogFragment implements Observer spannable.setSpan(new ClickableSpan() { @Override public void onClick(@NonNull View widget) { - Intent intent = new Intent(Settings.ACTION_WIRELESS_SETTINGS); - startActivity(intent); + try { + final Intent intent = new Intent(Intent.ACTION_MAIN, null); + intent.addCategory(Intent.CATEGORY_LAUNCHER); + final ComponentName cn = new ComponentName("com.android.settings", "com.android.settings.TetherSettings"); + intent.setComponent(cn); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } catch (ActivityNotFoundException e) { + Intent intent = new Intent(Settings.ACTION_WIRELESS_SETTINGS); + startActivity(intent); + } + } }, startIndex, endIndex, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); @@ -217,10 +229,10 @@ public class TetheringDialog extends AppCompatDialogFragment implements Observer getContext().getString(R.string.tethering_usb), PreferenceHelper.isUsbTetheringAllowed(getContext()), TetheringObservable.getInstance().isUsbTetheringEnabled()), -/* new DialogListAdapter.ViewModel(getContext().getResources().getDrawable(R.drawable.ic_bluetooth), + new DialogListAdapter.ViewModel(getContext().getResources().getDrawable(R.drawable.ic_bluetooth), getContext().getString(R.string.tethering_bluetooth), PreferenceHelper.isBluetoothTetheringAllowed(getContext()), - TetheringObservable.getInstance().isUsbTetheringEnabled())*/ + TetheringObservable.getInstance().isUsbTetheringEnabled()) }; } @@ -231,7 +243,7 @@ public class TetheringDialog extends AppCompatDialogFragment implements Observer Log.d(TAG, "TetheringObservable is updated"); dataset[0].enabled = observable.isWifiTetheringEnabled(); dataset[1].enabled = observable.isUsbTetheringEnabled(); -// dataset[2].enabled = observable.isBluetoothTetheringEnabled(); + dataset[2].enabled = observable.isBluetoothTetheringEnabled(); adapter.notifyDataSetChanged(); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringObservable.java b/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringObservable.java index 75d29417..9bca25e9 100644 --- a/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringObservable.java +++ b/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringObservable.java @@ -91,9 +91,16 @@ public class TetheringObservable extends Observable { } } - static void setBluetoothTethering(boolean enabled) { - if (getInstance().tetheringState.isBluetoothTetheringEnabled != enabled) { - getInstance().tetheringState.isBluetoothTetheringEnabled = enabled; + static void setBluetoothTethering(boolean enabled, @NonNull String address, @NonNull String interfaceName) { + if (getInstance().tetheringState.isBluetoothTetheringEnabled != enabled || + !getInstance().tetheringState.bluetoothAddress.equals(address) || + !getInstance().tetheringState.bluetoothInterface.equals(interfaceName)) { + TetheringState state = getInstance().tetheringState; + state.isBluetoothTetheringEnabled = enabled; + state.bluetoothAddress = address; + state.bluetoothInterface = interfaceName; + state.lastSeenBluetoothAddress = address.isEmpty() ? state.lastSeenBluetoothAddress : address; + state.lastSeenBluetoothInterface = interfaceName.isEmpty() ? state.lastSeenBluetoothInterface : interfaceName; getInstance().setChanged(); getInstance().notifyObservers(); } diff --git a/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringState.java b/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringState.java index 8ef237c6..21c378ee 100644 --- a/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringState.java +++ b/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringState.java @@ -1,5 +1,8 @@ package se.leap.bitmaskclient.tethering; +import de.blinkt.openvpn.core.VpnStatus; +import se.leap.bitmaskclient.eip.EipStatus; + public class TetheringState implements Cloneable { public boolean isWifiTetheringEnabled; public boolean isUsbTetheringEnabled; @@ -41,5 +44,9 @@ public class TetheringState implements Cloneable { return isVpnWifiTetheringAllowed || isVpnUsbTetheringAllowed || isVpnBluetoothTetheringAllowed; } + public boolean isVpnTetheringRunning() { + return (tetherWifiVpn() || tetherUsbVpn() || tetherBluetoothVpn()) && (EipStatus.getInstance().isConnecting() || EipStatus.getInstance().isConnected() || EipStatus.getInstance().isBlocking()); + } + } diff --git a/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringStateManager.java b/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringStateManager.java index d3c934f6..0af4c357 100644 --- a/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringStateManager.java +++ b/app/src/main/java/se/leap/bitmaskclient/tethering/TetheringStateManager.java @@ -85,7 +85,7 @@ public class TetheringStateManager { } static void updateBluetoothTetheringState() { - //TetheringObservable.setBluetoothTethering(isBluetoothTetheringEnabled()); + TetheringObservable.setBluetoothTethering(isBluetoothTetheringEnabled(), getBluetoothAddressRange(), getBluetoothInterfaceName()); } private static String getWifiAddressRange() { @@ -98,6 +98,11 @@ public class TetheringStateManager { return getAddressRange(interfaceAddress); } + private static String getBluetoothAddressRange() { + String interfaceAddress = getInterfaceAddress(getBluetoothInterface()); + return getAddressRange(interfaceAddress); + } + private static String getWlanInterfaceName() { return getInterfaceName(getWlanInterface()); } @@ -106,12 +111,20 @@ public class TetheringStateManager { return getInterfaceName(getUsbInterface()); } + private static String getBluetoothInterfaceName() { + return getInterfaceName(getBluetoothInterface()); + } + private static NetworkInterface getWlanInterface() { return getNetworkInterface(new String[]{"wlan", "eth"}); } private static NetworkInterface getUsbInterface() { - return getNetworkInterface(new String[]{"rndis", "usb"}); + return getNetworkInterface(new String[]{"rndis"}); + } + + private static NetworkInterface getBluetoothInterface() { + return getNetworkInterface(new String[]{"bt-pan"}); } private static boolean isBluetoothTetheringEnabled() { diff --git a/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java b/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java index 6f9744bc..238b00a1 100644 --- a/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java +++ b/app/src/main/java/se/leap/bitmaskclient/utils/PreferenceHelper.java @@ -29,7 +29,6 @@ import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY; import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES; import static se.leap.bitmaskclient.Constants.SHOW_EXPERIMENTAL; -import static se.leap.bitmaskclient.Constants.SU_PERMISSION; import static se.leap.bitmaskclient.Constants.USE_IPv6_FIREWALL; import static se.leap.bitmaskclient.Constants.USE_PLUGGABLE_TRANSPORTS; @@ -121,14 +120,6 @@ public class PreferenceHelper { apply(); } - public static boolean hasSuPermission(Context context) { - return getBoolean(context, SU_PERMISSION, false); - } - - public static void setSuPermission(Context context, boolean allowed) { - putBoolean(context, SU_PERMISSION, allowed); - } - public static boolean getUsePluggableTransports(Context context) { return getBoolean(context, USE_PLUGGABLE_TRANSPORTS, false); } diff --git a/app/src/main/java/se/leap/bitmaskclient/views/IconSwitchEntry.java b/app/src/main/java/se/leap/bitmaskclient/views/IconSwitchEntry.java index 02347b05..b41f3a50 100644 --- a/app/src/main/java/se/leap/bitmaskclient/views/IconSwitchEntry.java +++ b/app/src/main/java/se/leap/bitmaskclient/views/IconSwitchEntry.java @@ -7,6 +7,7 @@ import android.graphics.drawable.Drawable; import android.support.annotation.DrawableRes; import android.support.annotation.Nullable; import android.support.annotation.StringRes; +import android.support.v7.widget.AppCompatImageView; import android.support.v7.widget.SwitchCompat; import android.util.AttributeSet; import android.view.LayoutInflater; @@ -22,7 +23,7 @@ public class IconSwitchEntry extends LinearLayout { private TextView textView; private TextView subtitleView; - private ImageView iconView; + private AppCompatImageView iconView; private SwitchCompat switchView; private CompoundButton.OnCheckedChangeListener checkedChangeListener; @@ -88,6 +89,10 @@ public class IconSwitchEntry extends LinearLayout { textView.setText(id); } + public void showSubtitle(boolean show) { + subtitleView.setVisibility(show ? VISIBLE : GONE); + } + public void setIcon(@DrawableRes int id) { iconView.setImageResource(id); } @@ -101,4 +106,12 @@ public class IconSwitchEntry extends LinearLayout { switchView.setChecked(isChecked); switchView.setOnCheckedChangeListener(checkedChangeListener); } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + switchView.setVisibility(enabled ? VISIBLE : GONE); + textView.setTextColor(getResources().getColor(enabled ? android.R.color.black : R.color.colorDisabled)); + iconView.setImageAlpha(enabled ? 255 : 128); + } } diff --git a/app/src/main/res/layout-xlarge/v_switch_list_item.xml b/app/src/main/res/layout-xlarge/v_switch_list_item.xml index c393a0f8..8ad83650 100644 --- a/app/src/main/res/layout-xlarge/v_switch_list_item.xml +++ b/app/src/main/res/layout-xlarge/v_switch_list_item.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" > - <ImageView + <android.support.v7.widget.AppCompatImageView android:id="@+id/material_icon" android:layout_width="?android:attr/listPreferredItemHeight" android:layout_height="?android:attr/listPreferredItemHeight" diff --git a/app/src/main/res/layout/f_drawer_main.xml b/app/src/main/res/layout/f_drawer_main.xml index 191d547f..7e6291b7 100644 --- a/app/src/main/res/layout/f_drawer_main.xml +++ b/app/src/main/res/layout/f_drawer_main.xml @@ -65,6 +65,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" app:text="@string/save_battery" + app:subtitle="@string/subtitle_save_battery" app:icon="@drawable/ic_battery_36" /> diff --git a/app/src/main/res/layout/v_switch_list_item.xml b/app/src/main/res/layout/v_switch_list_item.xml index a92e78b1..6a2e4a8d 100644 --- a/app/src/main/res/layout/v_switch_list_item.xml +++ b/app/src/main/res/layout/v_switch_list_item.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" > - <ImageView + <android.support.v7.widget.AppCompatImageView android:id="@+id/material_icon" android:layout_width="?android:attr/listPreferredItemHeightSmall" android:layout_height="?android:attr/listPreferredItemHeightSmall" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b5fc2fa2..abab24df 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -102,6 +102,7 @@ <string name="vpn_certificate_is_invalid">VPN certificate is invalid. Try to download a new one.</string> <string name="vpn_certificate_user_message">The VPN certificate is invalid. Please log in to download a new one.</string> <string name="save_battery">Save battery</string> + <string name="subtitle_save_battery">Disabled while VPN Hotspot on</string> <string name="save_battery_message">Background data connections will hibernate when your phone is inactive.</string> <string name="always_on_vpn">Always-on VPN</string> <string name="subtitle_always_on_vpn">Open Android System Settings</string> @@ -139,4 +140,5 @@ <string name="warning_option_try_pt">Try obfuscated connection</string> <string name="warning_option_try_ovpn">Try standard connection</string> <string name="vpn_error_establish">Android failed to establish the VPN service.</string> + <string name="root_permission_error">%s cannot execute features like VPN Hotspot or IPv6 firewall without root permissions.</string> </resources> |