From 6af193f7d3c0fa3f73f5809442d83367bf025ffd Mon Sep 17 00:00:00 2001 From: cyBerta Date: Tue, 29 Dec 2020 01:01:05 +0100 Subject: move NavigationDrawerFragment into fragments directory --- .../se/leap/bitmaskclient/base/MainActivity.java | 2 +- .../base/drawer/NavigationDrawerFragment.java | 674 --------------------- .../base/fragments/NavigationDrawerFragment.java | 668 ++++++++++++++++++++ app/src/main/res/layout/a_main.xml | 2 +- app/src/main/res/layout/f_drawer_main.xml | 2 +- 5 files changed, 671 insertions(+), 677 deletions(-) delete mode 100644 app/src/main/java/se/leap/bitmaskclient/base/drawer/NavigationDrawerFragment.java create mode 100644 app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java diff --git a/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java index 1b7de10e..676e6c82 100644 --- a/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/base/MainActivity.java @@ -35,7 +35,7 @@ import java.util.Observable; import java.util.Observer; import se.leap.bitmaskclient.R; -import se.leap.bitmaskclient.base.drawer.NavigationDrawerFragment; +import se.leap.bitmaskclient.base.fragments.NavigationDrawerFragment; import se.leap.bitmaskclient.eip.EIP; import se.leap.bitmaskclient.eip.EipCommand; import se.leap.bitmaskclient.eip.EipSetupListener; diff --git a/app/src/main/java/se/leap/bitmaskclient/base/drawer/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/drawer/NavigationDrawerFragment.java deleted file mode 100644 index e70c87f9..00000000 --- a/app/src/main/java/se/leap/bitmaskclient/base/drawer/NavigationDrawerFragment.java +++ /dev/null @@ -1,674 +0,0 @@ -/** - * Copyright (c) 2019 LEAP Encryption Access Project and contributers - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package se.leap.bitmaskclient.base.drawer; - - -import android.app.Activity; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.res.Configuration; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import androidx.annotation.NonNull; -import androidx.annotation.StringRes; -import androidx.fragment.app.DialogFragment; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentTransaction; -import androidx.core.view.GravityCompat; -import androidx.drawerlayout.widget.DrawerLayout; -import androidx.appcompat.app.ActionBar; -import androidx.appcompat.app.ActionBarDrawerToggle; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.Toolbar; -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.TextView; - -import java.util.Observable; -import java.util.Observer; -import java.util.Set; - -import de.blinkt.openvpn.core.VpnStatus; -import se.leap.bitmaskclient.base.fragments.EipFragment; -import se.leap.bitmaskclient.base.FragmentManagerEnhanced; -import se.leap.bitmaskclient.base.MainActivity; -import se.leap.bitmaskclient.base.models.Provider; -import se.leap.bitmaskclient.providersetup.ProviderListActivity; -import se.leap.bitmaskclient.base.models.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.base.fragments.AboutFragment; -import se.leap.bitmaskclient.base.fragments.AlwaysOnDialog; -import se.leap.bitmaskclient.base.fragments.ExcludeAppsFragment; -import se.leap.bitmaskclient.base.fragments.LogFragment; -import se.leap.bitmaskclient.base.fragments.TetheringDialog; -import se.leap.bitmaskclient.tethering.TetheringObservable; -import se.leap.bitmaskclient.base.utils.PreferenceHelper; -import se.leap.bitmaskclient.base.views.IconSwitchEntry; -import se.leap.bitmaskclient.base.views.IconTextEntry; - -import static android.content.Context.MODE_PRIVATE; -import static android.view.View.GONE; -import static android.view.View.VISIBLE; -import static se.leap.bitmaskclient.base.BitmaskApp.getRefWatcher; -import static se.leap.bitmaskclient.base.models.Constants.DONATION_URL; -import static se.leap.bitmaskclient.base.models.Constants.ENABLE_DONATION; -import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; -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.models.Constants.USE_IPv6_FIREWALL; -import static se.leap.bitmaskclient.base.models.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; -import static se.leap.bitmaskclient.base.utils.ConfigHelper.isDefaultBitmask; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getSaveBattery; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getShowAlwaysOnDialog; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUsePluggableTransports; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.saveBattery; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.showExperimentalFeatures; -import static se.leap.bitmaskclient.base.utils.PreferenceHelper.usePluggableTransports; - -/** - * Fragment used for managing interactions for and presentation of a navigation drawer. - * See the - * design guidelines for a complete explanation of the behaviors implemented here. - */ -public class NavigationDrawerFragment extends Fragment implements SharedPreferences.OnSharedPreferenceChangeListener, Observer { - - /** - * 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"; - private static final String TAG = NavigationDrawerFragment.class.getName(); - public static final int TWO_SECONDS = 2000; - - /** - * Helper component that ties the action bar to the navigation drawer. - */ - private ActionBarDrawerToggle drawerToggle; - - private DrawerLayout drawerLayout; - private View drawerView; - private View fragmentContainerView; - private Toolbar toolbar; - private IconTextEntry account; - private IconSwitchEntry saveBattery; - private IconTextEntry tethering; - private IconSwitchEntry firewall; - private View experimentalFeatureFooter; - - private boolean userLearnedDrawer; - private volatile boolean wasPaused; - private volatile boolean shouldCloseOnResume; - - private SharedPreferences preferences; - - 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) { - super.onCreate(savedInstanceState); - // Reads in the flag indicating whether or not the user has demonstrated awareness of the - // drawer. See PREF_USER_LEARNED_DRAWER for details. - 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 - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - // Indicates 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) { - 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); - } - - @Override - public void onResume() { - super.onResume(); - wasPaused = false; - if (shouldCloseOnResume) { - closeDrawerWithDelay(); - } - } - - @Override - public void onPause() { - super.onPause(); - wasPaused = true; - } - - - - /** - * 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) { - final AppCompatActivity activity = (AppCompatActivity) getActivity(); - fragmentContainerView = activity.findViewById(fragmentId); - this.drawerLayout = drawerLayout; - // set a custom shadow that overlays the main content when the drawer opens - this.drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); - toolbar = this.drawerLayout.findViewById(R.id.toolbar); - - setupActionBar(); - setupEntries(); - setupActionBarDrawerToggle(activity); - - if (!userLearnedDrawer) { - openNavigationDrawerForFirstTimeUsers(); - } - - // Defer code dependent on restoration of previous instance state. - this.drawerLayout.post(() -> drawerToggle.syncState()); - this.drawerLayout.addDrawerListener(drawerToggle); - } - - private void setupActionBarDrawerToggle(final AppCompatActivity activity) { - // ActionBarDrawerToggle ties together the the proper interactions - // between the navigation drawer and the action bar app icon. - drawerToggle = new ActionBarDrawerToggle( - activity, - drawerLayout, - toolbar, - R.string.navigation_drawer_open, - R.string.navigation_drawer_close - ) { - @Override - public void onDrawerClosed(View drawerView) { - super.onDrawerClosed(drawerView); - if (!isAdded()) { - return; - } - activity.invalidateOptionsMenu(); - } - - @Override - public void onDrawerOpened(View drawerView) { - super.onDrawerOpened(drawerView); - if (!isAdded()) { - return; - } - - if (!userLearnedDrawer) { - // The user manually opened the drawer; store this flag to prevent auto-showing - // the navigation drawer automatically in the future. - userLearnedDrawer = true; - preferences.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).apply(); - } - activity.invalidateOptionsMenu(); - } - }; - } - - private void setupEntries() { - initAccountEntry(); - initSwitchProviderEntry(); - initUseBridgesEntry(); - initSaveBatteryEntry(); - initAlwaysOnVpnEntry(); - initExcludeAppsEntry(); - initShowExperimentalHint(); - initTetheringEntry(); - initFirewallEntry(); - initExperimentalFeatureFooter(); - initDonateEntry(); - initLogEntry(); - initAboutEntry(); - } - - private void initAccountEntry() { - account = drawerView.findViewById(R.id.account); - FragmentManagerEnhanced fragmentManager = new FragmentManagerEnhanced(getActivity().getSupportFragmentManager()); - Provider currentProvider = ProviderObservable.getInstance().getCurrentProvider(); - account.setText(currentProvider.getName()); - account.setOnClickListener((buttonView) -> { - Fragment fragment = new EipFragment(); - Bundle arguments = new Bundle(); - arguments.putParcelable(PROVIDER_KEY, currentProvider); - fragment.setArguments(arguments); - hideActionBarSubTitle(); - fragmentManager.replace(R.id.main_container, fragment, MainActivity.TAG); - closeDrawer(); - }); - } - - private void initSwitchProviderEntry() { - if (isDefaultBitmask()) { - IconTextEntry switchProvider = drawerView.findViewById(R.id.switch_provider); - switchProvider.setVisibility(VISIBLE); - switchProvider.setOnClickListener(v -> - getActivity().startActivityForResult(new Intent(getActivity(), ProviderListActivity.class), REQUEST_CODE_SWITCH_PROVIDER)); - } - } - - private void initUseBridgesEntry() { - IconSwitchEntry useBridges = drawerView.findViewById(R.id.bridges_switch); - if (ProviderObservable.getInstance().getCurrentProvider().supportsPluggableTransports()) { - useBridges.setVisibility(VISIBLE); - useBridges.setChecked(getUsePluggableTransports(getContext())); - useBridges.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (!buttonView.isPressed()) { - return; - } - usePluggableTransports(getContext(), isChecked); - if (VpnStatus.isVPNActive()) { - EipCommand.startVPN(getContext(), false); - closeDrawer(); - } - }); - - - } else { - useBridges.setVisibility(GONE); - } - } - - private void initSaveBatteryEntry() { - saveBattery = drawerView.findViewById(R.id.battery_switch); - saveBattery.showSubtitle(false); - saveBattery.setChecked(getSaveBattery(getContext())); - saveBattery.setOnCheckedChangeListener(((buttonView, isChecked) -> { - if (!buttonView.isPressed()) { - return; - } - if (isChecked) { - showSaveBatteryAlert(); - } else { - 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() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - IconTextEntry alwaysOnVpn = drawerView.findViewById(R.id.always_on_vpn); - alwaysOnVpn.setVisibility(VISIBLE); - alwaysOnVpn.setOnClickListener((buttonView) -> { - closeDrawer(); - if (getShowAlwaysOnDialog(getContext())) { - showAlwaysOnDialog(); - } else { - Intent intent = new Intent("android.net.vpn.SETTINGS"); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - } - }); - } - } - - private void initExcludeAppsEntry() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - IconTextEntry excludeApps = drawerView.findViewById(R.id.exclude_apps); - excludeApps.setVisibility(VISIBLE); - Set apps = PreferenceHelper.getExcludedApps(this.getContext()); - if (apps != null) { - updateExcludeAppsSubtitle(excludeApps, apps.size()); - } - FragmentManagerEnhanced fragmentManager = new FragmentManagerEnhanced(getActivity().getSupportFragmentManager()); - excludeApps.setOnClickListener((buttonView) -> { - closeDrawer(); - Fragment fragment = new ExcludeAppsFragment(); - setActionBarTitle(exclude_apps_fragment_title); - fragmentManager.replace(R.id.main_container, fragment, MainActivity.TAG); - }); - } - } - - private void initShowExperimentalHint() { - TextView textView = drawerLayout.findViewById(R.id.show_experimental_features); - textView.setText(showExperimentalFeatures(getContext()) ? R.string.hide_experimental : R.string.show_experimental); - textView.setOnClickListener(v -> { - boolean shown = showExperimentalFeatures(getContext()); - if (shown) { - tethering.setVisibility(GONE); - firewall.setVisibility(GONE); - experimentalFeatureFooter.setVisibility(GONE); - ((TextView) v).setText(R.string.show_experimental); - } else { - tethering.setVisibility(VISIBLE); - firewall.setVisibility(VISIBLE); - experimentalFeatureFooter.setVisibility(VISIBLE); - ((TextView) v).setText(R.string.hide_experimental); - } - PreferenceHelper.setShowExperimentalFeatures(getContext(), !shown); - }); - } - - private void initFirewallEntry() { - firewall = drawerView.findViewById(R.id.enableIPv6Firewall); - boolean show = showExperimentalFeatures(getContext()); - firewall.setVisibility(show ? VISIBLE : GONE); - firewall.setChecked(PreferenceHelper.useIpv6Firewall(getContext())); - firewall.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (!buttonView.isPressed()) { - return; - } - PreferenceHelper.setUseIPv6Firewall(getContext(), isChecked); - if (VpnStatus.isVPNActive()) { - if (isChecked) { - firewallManager.startIPv6Firewall(); - } else { - firewallManager.stopIPv6Firewall(); - } - } - }); - } - - private void initTetheringEntry() { - tethering = drawerView.findViewById(R.id.tethering); - boolean show = showExperimentalFeatures(getContext()); - tethering.setVisibility(show ? VISIBLE : GONE); - tethering.setOnClickListener((buttonView) -> { - showTetheringAlert(); - }); - } - - private void initExperimentalFeatureFooter() { - experimentalFeatureFooter = drawerView.findViewById(R.id.experimental_features_footer); - boolean show = showExperimentalFeatures(getContext()); - experimentalFeatureFooter.setVisibility(show ? VISIBLE : GONE); - } - - private void initDonateEntry() { - if (ENABLE_DONATION) { - IconTextEntry donate = drawerView.findViewById(R.id.donate); - donate.setVisibility(VISIBLE); - donate.setOnClickListener((buttonView) -> { - closeDrawer(); - Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(DONATION_URL)); - startActivity(browserIntent); - - }); - } - } - - private void initLogEntry() { - IconTextEntry log = drawerView.findViewById(R.id.log); - FragmentManagerEnhanced fragmentManager = new FragmentManagerEnhanced(getActivity().getSupportFragmentManager()); - log.setOnClickListener((buttonView) -> { - closeDrawer(); - Fragment fragment = new LogFragment(); - setActionBarTitle(log_fragment_title); - fragmentManager.replace(R.id.main_container, fragment, MainActivity.TAG); - }); - } - - private void initAboutEntry() { - IconTextEntry about = drawerView.findViewById(R.id.about); - FragmentManagerEnhanced fragmentManager = new FragmentManagerEnhanced(getActivity().getSupportFragmentManager()); - about.setOnClickListener((buttonView) -> { - closeDrawer(); - Fragment fragment = new AboutFragment(); - setActionBarTitle(about_fragment_title); - fragmentManager.replace(R.id.main_container, fragment, MainActivity.TAG); - }); - } - - private void closeDrawer() { - if (drawerLayout != null) { - drawerLayout.closeDrawer(fragmentContainerView); - } - } - - private ActionBar setupActionBar() { - AppCompatActivity activity = (AppCompatActivity) getActivity(); - activity.setSupportActionBar(toolbar); - final ActionBar actionBar = activity.getSupportActionBar(); - actionBar.setDisplayHomeAsUpEnabled(true); - actionBar.setHomeButtonEnabled(true); - actionBar.setDisplayShowTitleEnabled(true); - return actionBar; - } - - private void openNavigationDrawerForFirstTimeUsers() { - if (userLearnedDrawer) { - return; - } - - drawerLayout.openDrawer(fragmentContainerView, false); - closeDrawerWithDelay(); - } - - @NonNull - private void closeDrawerWithDelay() { - final Handler navigationDrawerHandler = new Handler(); - navigationDrawerHandler.postDelayed(() -> { - if (!wasPaused) { - drawerLayout.closeDrawer(fragmentContainerView, true); - } else { - shouldCloseOnResume = true; - } - - }, TWO_SECONDS); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - if (showSaveBattery) { - outState.putBoolean(KEY_SHOW_SAVE_BATTERY_ALERT, true); - alertDialog.dismiss(); - } - } - - private void restoreFromSavedInstance(Bundle savedInstanceState) { - if (savedInstanceState != null && savedInstanceState.containsKey(KEY_SHOW_SAVE_BATTERY_ALERT)) { - showSaveBatteryAlert(); - } - } - - private void showSaveBatteryAlert() { - Activity activity = getActivity(); - if (activity == null) { - return; - } - - try { - AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity()); - showSaveBattery = true; - alertDialog = alertBuilder - .setTitle(activity.getString(R.string.save_battery)) - .setMessage(activity.getString(R.string.save_battery_message)) - .setPositiveButton((android.R.string.yes), (dialog, which) -> { - saveBattery(getContext(), true); - }) - .setNegativeButton(activity.getString(android.R.string.no), (dialog, which) -> saveBattery.setCheckedQuietly(false)) - .setOnDismissListener(dialog -> showSaveBattery = false) - .setOnCancelListener(dialog -> saveBattery.setCheckedQuietly(false)).show(); - } catch (IllegalStateException e) { - e.printStackTrace(); - } - } - - public void showTetheringAlert() { - try { - - FragmentTransaction fragmentTransaction = new FragmentManagerEnhanced( - getActivity().getSupportFragmentManager()).removePreviousFragment( - TetheringDialog.TAG); - DialogFragment newFragment = new TetheringDialog(); - newFragment.show(fragmentTransaction, TetheringDialog.TAG); - } catch (IllegalStateException | NullPointerException e) { - e.printStackTrace(); - } - } - - public void showAlwaysOnDialog() { - try { - - FragmentTransaction fragmentTransaction = new FragmentManagerEnhanced( - getActivity().getSupportFragmentManager()).removePreviousFragment( - AlwaysOnDialog.TAG); - DialogFragment newFragment = new AlwaysOnDialog(); - newFragment.show(fragmentTransaction, AlwaysOnDialog.TAG); - } catch (IllegalStateException | NullPointerException e) { - e.printStackTrace(); - } - - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - // Forward the new configuration the drawer toggle component. - drawerToggle.onConfigurationChanged(newConfig); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - if (drawerLayout != null && isDrawerOpen()) { - showGlobalContextActionBar(); - } - super.onCreateOptionsMenu(menu, inflater); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (drawerToggle.onOptionsItemSelected(item)) { - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onDestroy() { - super.onDestroy(); - getRefWatcher(getActivity()).watch(this); - preferences.unregisterOnSharedPreferenceChangeListener(this); - } - - /** - * Per the navigation drawer design guidelines, updates the action bar to show the global app - * 'context', rather than just what's in the current screen. - */ - private void showGlobalContextActionBar() { - ActionBar actionBar = getActionBar(); - actionBar.setDisplayShowTitleEnabled(true); - actionBar.setTitle(R.string.app_name); - } - - private ActionBar getActionBar() { - return ((AppCompatActivity) getActivity()).getSupportActionBar(); - } - - private void setActionBarTitle(@StringRes int resId) { - ActionBar actionBar = getActionBar(); - if (actionBar != null) { - actionBar.setSubtitle(resId); - } - } - - private void hideActionBarSubTitle() { - ActionBar actionBar = getActionBar(); - if (actionBar != null) { - actionBar.setSubtitle(null); - } - } - - public void refresh() { - Provider currentProvider = ProviderObservable.getInstance().getCurrentProvider(); - account.setText(currentProvider.getName()); - initUseBridgesEntry(); - } - - private void updateExcludeAppsSubtitle(IconTextEntry excludeApps, int number) { - if (number > 0) { - excludeApps.setSubtitle(getContext().getResources().getQuantityString(R.plurals.subtitle_exclude_apps, number, number)); - excludeApps.setSubtitleColor(R.color.colorError); - } else { - excludeApps.hideSubtitle(); - } - } - - public void onAppsExcluded(int number) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - IconTextEntry excludeApps = drawerView.findViewById(R.id.exclude_apps); - updateExcludeAppsSubtitle(excludeApps, number); - } - } - - @Override - public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { - 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/base/fragments/NavigationDrawerFragment.java b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java new file mode 100644 index 00000000..2ce0e597 --- /dev/null +++ b/app/src/main/java/se/leap/bitmaskclient/base/fragments/NavigationDrawerFragment.java @@ -0,0 +1,668 @@ +/** + * Copyright (c) 2019 LEAP Encryption Access Project and contributers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package se.leap.bitmaskclient.base.fragments; + + +import android.app.Activity; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; +import androidx.core.view.GravityCompat; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.ActionBarDrawerToggle; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +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.TextView; + +import java.util.Observable; +import java.util.Observer; +import java.util.Set; + +import de.blinkt.openvpn.core.VpnStatus; +import se.leap.bitmaskclient.base.FragmentManagerEnhanced; +import se.leap.bitmaskclient.base.MainActivity; +import se.leap.bitmaskclient.base.models.Provider; +import se.leap.bitmaskclient.providersetup.ProviderListActivity; +import se.leap.bitmaskclient.base.models.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.tethering.TetheringObservable; +import se.leap.bitmaskclient.base.utils.PreferenceHelper; +import se.leap.bitmaskclient.base.views.IconSwitchEntry; +import se.leap.bitmaskclient.base.views.IconTextEntry; + +import static android.content.Context.MODE_PRIVATE; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; +import static se.leap.bitmaskclient.base.BitmaskApp.getRefWatcher; +import static se.leap.bitmaskclient.base.models.Constants.DONATION_URL; +import static se.leap.bitmaskclient.base.models.Constants.ENABLE_DONATION; +import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; +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.models.Constants.USE_IPv6_FIREWALL; +import static se.leap.bitmaskclient.base.models.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; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.isDefaultBitmask; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getSaveBattery; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getShowAlwaysOnDialog; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUsePluggableTransports; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.saveBattery; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.showExperimentalFeatures; +import static se.leap.bitmaskclient.base.utils.PreferenceHelper.usePluggableTransports; + +/** + * Fragment used for managing interactions for and presentation of a navigation drawer. + * See the + * design guidelines for a complete explanation of the behaviors implemented here. + */ +public class NavigationDrawerFragment extends Fragment implements SharedPreferences.OnSharedPreferenceChangeListener, Observer { + + /** + * 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"; + private static final String TAG = NavigationDrawerFragment.class.getName(); + public static final int TWO_SECONDS = 2000; + + /** + * Helper component that ties the action bar to the navigation drawer. + */ + private ActionBarDrawerToggle drawerToggle; + + private DrawerLayout drawerLayout; + private View drawerView; + private View fragmentContainerView; + private Toolbar toolbar; + private IconTextEntry account; + private IconSwitchEntry saveBattery; + private IconTextEntry tethering; + private IconSwitchEntry firewall; + private View experimentalFeatureFooter; + + private boolean userLearnedDrawer; + private volatile boolean wasPaused; + private volatile boolean shouldCloseOnResume; + + private SharedPreferences preferences; + + 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) { + super.onCreate(savedInstanceState); + // Reads in the flag indicating whether or not the user has demonstrated awareness of the + // drawer. See PREF_USER_LEARNED_DRAWER for details. + 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 + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + // Indicates 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) { + 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); + } + + @Override + public void onResume() { + super.onResume(); + wasPaused = false; + if (shouldCloseOnResume) { + closeDrawerWithDelay(); + } + } + + @Override + public void onPause() { + super.onPause(); + wasPaused = true; + } + + + + /** + * 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) { + final AppCompatActivity activity = (AppCompatActivity) getActivity(); + fragmentContainerView = activity.findViewById(fragmentId); + this.drawerLayout = drawerLayout; + // set a custom shadow that overlays the main content when the drawer opens + this.drawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); + toolbar = this.drawerLayout.findViewById(R.id.toolbar); + + setupActionBar(); + setupEntries(); + setupActionBarDrawerToggle(activity); + + if (!userLearnedDrawer) { + openNavigationDrawerForFirstTimeUsers(); + } + + // Defer code dependent on restoration of previous instance state. + this.drawerLayout.post(() -> drawerToggle.syncState()); + this.drawerLayout.addDrawerListener(drawerToggle); + } + + private void setupActionBarDrawerToggle(final AppCompatActivity activity) { + // ActionBarDrawerToggle ties together the the proper interactions + // between the navigation drawer and the action bar app icon. + drawerToggle = new ActionBarDrawerToggle( + activity, + drawerLayout, + toolbar, + R.string.navigation_drawer_open, + R.string.navigation_drawer_close + ) { + @Override + public void onDrawerClosed(View drawerView) { + super.onDrawerClosed(drawerView); + if (!isAdded()) { + return; + } + activity.invalidateOptionsMenu(); + } + + @Override + public void onDrawerOpened(View drawerView) { + super.onDrawerOpened(drawerView); + if (!isAdded()) { + return; + } + + if (!userLearnedDrawer) { + // The user manually opened the drawer; store this flag to prevent auto-showing + // the navigation drawer automatically in the future. + userLearnedDrawer = true; + preferences.edit().putBoolean(PREF_USER_LEARNED_DRAWER, true).apply(); + } + activity.invalidateOptionsMenu(); + } + }; + } + + private void setupEntries() { + initAccountEntry(); + initSwitchProviderEntry(); + initUseBridgesEntry(); + initSaveBatteryEntry(); + initAlwaysOnVpnEntry(); + initExcludeAppsEntry(); + initShowExperimentalHint(); + initTetheringEntry(); + initFirewallEntry(); + initExperimentalFeatureFooter(); + initDonateEntry(); + initLogEntry(); + initAboutEntry(); + } + + private void initAccountEntry() { + account = drawerView.findViewById(R.id.account); + FragmentManagerEnhanced fragmentManager = new FragmentManagerEnhanced(getActivity().getSupportFragmentManager()); + Provider currentProvider = ProviderObservable.getInstance().getCurrentProvider(); + account.setText(currentProvider.getName()); + account.setOnClickListener((buttonView) -> { + Fragment fragment = new EipFragment(); + Bundle arguments = new Bundle(); + arguments.putParcelable(PROVIDER_KEY, currentProvider); + fragment.setArguments(arguments); + hideActionBarSubTitle(); + fragmentManager.replace(R.id.main_container, fragment, MainActivity.TAG); + closeDrawer(); + }); + } + + private void initSwitchProviderEntry() { + if (isDefaultBitmask()) { + IconTextEntry switchProvider = drawerView.findViewById(R.id.switch_provider); + switchProvider.setVisibility(VISIBLE); + switchProvider.setOnClickListener(v -> + getActivity().startActivityForResult(new Intent(getActivity(), ProviderListActivity.class), REQUEST_CODE_SWITCH_PROVIDER)); + } + } + + private void initUseBridgesEntry() { + IconSwitchEntry useBridges = drawerView.findViewById(R.id.bridges_switch); + if (ProviderObservable.getInstance().getCurrentProvider().supportsPluggableTransports()) { + useBridges.setVisibility(VISIBLE); + useBridges.setChecked(getUsePluggableTransports(getContext())); + useBridges.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (!buttonView.isPressed()) { + return; + } + usePluggableTransports(getContext(), isChecked); + if (VpnStatus.isVPNActive()) { + EipCommand.startVPN(getContext(), false); + closeDrawer(); + } + }); + + + } else { + useBridges.setVisibility(GONE); + } + } + + private void initSaveBatteryEntry() { + saveBattery = drawerView.findViewById(R.id.battery_switch); + saveBattery.showSubtitle(false); + saveBattery.setChecked(getSaveBattery(getContext())); + saveBattery.setOnCheckedChangeListener(((buttonView, isChecked) -> { + if (!buttonView.isPressed()) { + return; + } + if (isChecked) { + showSaveBatteryAlert(); + } else { + 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() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + IconTextEntry alwaysOnVpn = drawerView.findViewById(R.id.always_on_vpn); + alwaysOnVpn.setVisibility(VISIBLE); + alwaysOnVpn.setOnClickListener((buttonView) -> { + closeDrawer(); + if (getShowAlwaysOnDialog(getContext())) { + showAlwaysOnDialog(); + } else { + Intent intent = new Intent("android.net.vpn.SETTINGS"); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + }); + } + } + + private void initExcludeAppsEntry() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + IconTextEntry excludeApps = drawerView.findViewById(R.id.exclude_apps); + excludeApps.setVisibility(VISIBLE); + Set apps = PreferenceHelper.getExcludedApps(this.getContext()); + if (apps != null) { + updateExcludeAppsSubtitle(excludeApps, apps.size()); + } + FragmentManagerEnhanced fragmentManager = new FragmentManagerEnhanced(getActivity().getSupportFragmentManager()); + excludeApps.setOnClickListener((buttonView) -> { + closeDrawer(); + Fragment fragment = new ExcludeAppsFragment(); + setActionBarTitle(exclude_apps_fragment_title); + fragmentManager.replace(R.id.main_container, fragment, MainActivity.TAG); + }); + } + } + + private void initShowExperimentalHint() { + TextView textView = drawerLayout.findViewById(R.id.show_experimental_features); + textView.setText(showExperimentalFeatures(getContext()) ? R.string.hide_experimental : R.string.show_experimental); + textView.setOnClickListener(v -> { + boolean shown = showExperimentalFeatures(getContext()); + if (shown) { + tethering.setVisibility(GONE); + firewall.setVisibility(GONE); + experimentalFeatureFooter.setVisibility(GONE); + ((TextView) v).setText(R.string.show_experimental); + } else { + tethering.setVisibility(VISIBLE); + firewall.setVisibility(VISIBLE); + experimentalFeatureFooter.setVisibility(VISIBLE); + ((TextView) v).setText(R.string.hide_experimental); + } + PreferenceHelper.setShowExperimentalFeatures(getContext(), !shown); + }); + } + + private void initFirewallEntry() { + firewall = drawerView.findViewById(R.id.enableIPv6Firewall); + boolean show = showExperimentalFeatures(getContext()); + firewall.setVisibility(show ? VISIBLE : GONE); + firewall.setChecked(PreferenceHelper.useIpv6Firewall(getContext())); + firewall.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (!buttonView.isPressed()) { + return; + } + PreferenceHelper.setUseIPv6Firewall(getContext(), isChecked); + if (VpnStatus.isVPNActive()) { + if (isChecked) { + firewallManager.startIPv6Firewall(); + } else { + firewallManager.stopIPv6Firewall(); + } + } + }); + } + + private void initTetheringEntry() { + tethering = drawerView.findViewById(R.id.tethering); + boolean show = showExperimentalFeatures(getContext()); + tethering.setVisibility(show ? VISIBLE : GONE); + tethering.setOnClickListener((buttonView) -> { + showTetheringAlert(); + }); + } + + private void initExperimentalFeatureFooter() { + experimentalFeatureFooter = drawerView.findViewById(R.id.experimental_features_footer); + boolean show = showExperimentalFeatures(getContext()); + experimentalFeatureFooter.setVisibility(show ? VISIBLE : GONE); + } + + private void initDonateEntry() { + if (ENABLE_DONATION) { + IconTextEntry donate = drawerView.findViewById(R.id.donate); + donate.setVisibility(VISIBLE); + donate.setOnClickListener((buttonView) -> { + closeDrawer(); + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(DONATION_URL)); + startActivity(browserIntent); + + }); + } + } + + private void initLogEntry() { + IconTextEntry log = drawerView.findViewById(R.id.log); + FragmentManagerEnhanced fragmentManager = new FragmentManagerEnhanced(getActivity().getSupportFragmentManager()); + log.setOnClickListener((buttonView) -> { + closeDrawer(); + Fragment fragment = new LogFragment(); + setActionBarTitle(log_fragment_title); + fragmentManager.replace(R.id.main_container, fragment, MainActivity.TAG); + }); + } + + private void initAboutEntry() { + IconTextEntry about = drawerView.findViewById(R.id.about); + FragmentManagerEnhanced fragmentManager = new FragmentManagerEnhanced(getActivity().getSupportFragmentManager()); + about.setOnClickListener((buttonView) -> { + closeDrawer(); + Fragment fragment = new AboutFragment(); + setActionBarTitle(about_fragment_title); + fragmentManager.replace(R.id.main_container, fragment, MainActivity.TAG); + }); + } + + private void closeDrawer() { + if (drawerLayout != null) { + drawerLayout.closeDrawer(fragmentContainerView); + } + } + + private ActionBar setupActionBar() { + AppCompatActivity activity = (AppCompatActivity) getActivity(); + activity.setSupportActionBar(toolbar); + final ActionBar actionBar = activity.getSupportActionBar(); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setHomeButtonEnabled(true); + actionBar.setDisplayShowTitleEnabled(true); + return actionBar; + } + + private void openNavigationDrawerForFirstTimeUsers() { + if (userLearnedDrawer) { + return; + } + + drawerLayout.openDrawer(fragmentContainerView, false); + closeDrawerWithDelay(); + } + + @NonNull + private void closeDrawerWithDelay() { + final Handler navigationDrawerHandler = new Handler(); + navigationDrawerHandler.postDelayed(() -> { + if (!wasPaused) { + drawerLayout.closeDrawer(fragmentContainerView, true); + } else { + shouldCloseOnResume = true; + } + + }, TWO_SECONDS); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + if (showSaveBattery) { + outState.putBoolean(KEY_SHOW_SAVE_BATTERY_ALERT, true); + alertDialog.dismiss(); + } + } + + private void restoreFromSavedInstance(Bundle savedInstanceState) { + if (savedInstanceState != null && savedInstanceState.containsKey(KEY_SHOW_SAVE_BATTERY_ALERT)) { + showSaveBatteryAlert(); + } + } + + private void showSaveBatteryAlert() { + Activity activity = getActivity(); + if (activity == null) { + return; + } + + try { + AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivity()); + showSaveBattery = true; + alertDialog = alertBuilder + .setTitle(activity.getString(R.string.save_battery)) + .setMessage(activity.getString(R.string.save_battery_message)) + .setPositiveButton((android.R.string.yes), (dialog, which) -> { + saveBattery(getContext(), true); + }) + .setNegativeButton(activity.getString(android.R.string.no), (dialog, which) -> saveBattery.setCheckedQuietly(false)) + .setOnDismissListener(dialog -> showSaveBattery = false) + .setOnCancelListener(dialog -> saveBattery.setCheckedQuietly(false)).show(); + } catch (IllegalStateException e) { + e.printStackTrace(); + } + } + + public void showTetheringAlert() { + try { + + FragmentTransaction fragmentTransaction = new FragmentManagerEnhanced( + getActivity().getSupportFragmentManager()).removePreviousFragment( + TetheringDialog.TAG); + DialogFragment newFragment = new TetheringDialog(); + newFragment.show(fragmentTransaction, TetheringDialog.TAG); + } catch (IllegalStateException | NullPointerException e) { + e.printStackTrace(); + } + } + + public void showAlwaysOnDialog() { + try { + + FragmentTransaction fragmentTransaction = new FragmentManagerEnhanced( + getActivity().getSupportFragmentManager()).removePreviousFragment( + AlwaysOnDialog.TAG); + DialogFragment newFragment = new AlwaysOnDialog(); + newFragment.show(fragmentTransaction, AlwaysOnDialog.TAG); + } catch (IllegalStateException | NullPointerException e) { + e.printStackTrace(); + } + + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + // Forward the new configuration the drawer toggle component. + drawerToggle.onConfigurationChanged(newConfig); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + if (drawerLayout != null && isDrawerOpen()) { + showGlobalContextActionBar(); + } + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (drawerToggle.onOptionsItemSelected(item)) { + return true; + } + return super.onOptionsItemSelected(item); + } + + @Override + public void onDestroy() { + super.onDestroy(); + getRefWatcher(getActivity()).watch(this); + preferences.unregisterOnSharedPreferenceChangeListener(this); + } + + /** + * Per the navigation drawer design guidelines, updates the action bar to show the global app + * 'context', rather than just what's in the current screen. + */ + private void showGlobalContextActionBar() { + ActionBar actionBar = getActionBar(); + actionBar.setDisplayShowTitleEnabled(true); + actionBar.setTitle(R.string.app_name); + } + + private ActionBar getActionBar() { + return ((AppCompatActivity) getActivity()).getSupportActionBar(); + } + + private void setActionBarTitle(@StringRes int resId) { + ActionBar actionBar = getActionBar(); + if (actionBar != null) { + actionBar.setSubtitle(resId); + } + } + + private void hideActionBarSubTitle() { + ActionBar actionBar = getActionBar(); + if (actionBar != null) { + actionBar.setSubtitle(null); + } + } + + public void refresh() { + Provider currentProvider = ProviderObservable.getInstance().getCurrentProvider(); + account.setText(currentProvider.getName()); + initUseBridgesEntry(); + } + + private void updateExcludeAppsSubtitle(IconTextEntry excludeApps, int number) { + if (number > 0) { + excludeApps.setSubtitle(getContext().getResources().getQuantityString(R.plurals.subtitle_exclude_apps, number, number)); + excludeApps.setSubtitleColor(R.color.colorError); + } else { + excludeApps.hideSubtitle(); + } + } + + public void onAppsExcluded(int number) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + IconTextEntry excludeApps = drawerView.findViewById(R.id.exclude_apps); + updateExcludeAppsSubtitle(excludeApps, number); + } + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + 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/res/layout/a_main.xml b/app/src/main/res/layout/a_main.xml index 4e921a9c..0e30d2a8 100644 --- a/app/src/main/res/layout/a_main.xml +++ b/app/src/main/res/layout/a_main.xml @@ -34,7 +34,7 @@ the container. -->