From e4940a046ae36d3f9d5175b6d1f71812887f742e Mon Sep 17 00:00:00 2001 From: akwizgran Date: Fri, 24 Apr 2020 15:22:49 +0100 Subject: [PATCH] Move transport toggles back to the settings screen. --- .../android/navdrawer/NavDrawerActivity.java | 138 ++++++++++- .../android/navdrawer/NavDrawerModule.java | 4 + .../android/navdrawer/NavDrawerViewModel.java | 117 +--------- .../navdrawer/PluginViewController.java | 200 ---------------- .../android/navdrawer/PluginViewModel.java | 215 ++++++++++++++++++ .../android/settings/SettingsFragment.java | 76 ++++++- .../res/drawable/ic_baseline_drag_handle.xml | 12 - .../src/main/res/layout/navigation_menu.xml | 53 ++++- .../res/layout/navigation_menu_collapsed.xml | 165 -------------- .../res/layout/navigation_menu_expanded.xml | 183 --------------- .../src/main/res/layout/transports_list.xml | 19 ++ briar-android/src/main/res/values/strings.xml | 25 +- briar-android/src/main/res/xml/settings.xml | 28 +++ 13 files changed, 530 insertions(+), 705 deletions(-) delete mode 100644 briar-android/src/main/java/org/briarproject/briar/android/navdrawer/PluginViewController.java create mode 100644 briar-android/src/main/java/org/briarproject/briar/android/navdrawer/PluginViewModel.java delete mode 100644 briar-android/src/main/res/drawable/ic_baseline_drag_handle.xml delete mode 100644 briar-android/src/main/res/layout/navigation_menu_collapsed.xml delete mode 100644 briar-android/src/main/res/layout/navigation_menu_expanded.xml create mode 100644 briar-android/src/main/res/layout/transports_list.xml diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java index 0ea593c9e..1ea8f9772 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java @@ -4,9 +4,12 @@ import android.content.Intent; import android.content.res.Configuration; import android.net.Uri; import android.os.Bundle; +import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.GridView; import android.widget.ImageView; import android.widget.TextView; @@ -17,6 +20,11 @@ import org.briarproject.bramble.api.db.DbException; import org.briarproject.bramble.api.lifecycle.LifecycleManager; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.bramble.api.plugin.BluetoothConstants; +import org.briarproject.bramble.api.plugin.LanTcpConstants; +import org.briarproject.bramble.api.plugin.Plugin.State; +import org.briarproject.bramble.api.plugin.TorConstants; +import org.briarproject.bramble.api.plugin.TransportId; import org.briarproject.briar.R; import org.briarproject.briar.android.activity.ActivityComponent; import org.briarproject.briar.android.activity.BriarActivity; @@ -29,16 +37,21 @@ import org.briarproject.briar.android.logout.SignOutFragment; import org.briarproject.briar.android.privategroup.list.GroupListFragment; import org.briarproject.briar.android.settings.SettingsActivity; +import java.util.ArrayList; +import java.util.List; import java.util.logging.Logger; import javax.inject.Inject; +import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.widget.Toolbar; import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; @@ -53,6 +66,9 @@ import static androidx.fragment.app.FragmentManager.POP_BACK_STACK_INCLUSIVE; import static java.util.Objects.requireNonNull; import static java.util.logging.Logger.getLogger; import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING; +import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; +import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING; +import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; import static org.briarproject.briar.android.BriarService.EXTRA_STARTUP_FAILED; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD; import static org.briarproject.briar.android.navdrawer.IntentRouter.handleExternalIntent; @@ -79,7 +95,8 @@ public class NavDrawerActivity extends BriarActivity implements public static Uri SIGN_OUT_URI = Uri.parse("briar-content://org.briarproject.briar/sign-out"); - private NavDrawerViewModel viewModel; + private NavDrawerViewModel navDrawerViewModel; + private PluginViewModel pluginViewModel; private ActionBarDrawerToggle drawerToggle; @Inject @@ -91,6 +108,9 @@ public class NavDrawerActivity extends BriarActivity implements private DrawerLayout drawerLayout; private NavigationView navigation; + private List transports; + private BaseAdapter transportsAdapter; + @Override public void injectActivity(ActivityComponent component) { component.inject(this); @@ -102,20 +122,21 @@ public class NavDrawerActivity extends BriarActivity implements exitIfStartupFailed(getIntent()); setContentView(R.layout.activity_nav_drawer); - viewModel = ViewModelProviders.of(this, viewModelFactory) - .get(NavDrawerViewModel.class); + ViewModelProvider provider = + ViewModelProviders.of(this, viewModelFactory); + navDrawerViewModel = provider.get(NavDrawerViewModel.class); + pluginViewModel = provider.get(PluginViewModel.class); - viewModel.showExpiryWarning().observe(this, this::showExpiryWarning); - viewModel.shouldAskForDozeWhitelisting().observe(this, ask -> { + navDrawerViewModel.showExpiryWarning() + .observe(this, this::showExpiryWarning); + navDrawerViewModel.shouldAskForDozeWhitelisting().observe(this, ask -> { if (ask) showDozeDialog(getString(R.string.setup_doze_intro)); }); - View drawerScrollView = findViewById(R.id.drawerScrollView); - new PluginViewController(drawerScrollView, this, viewModel); - Toolbar toolbar = findViewById(R.id.toolbar); drawerLayout = findViewById(R.id.drawer_layout); navigation = findViewById(R.id.navigation); + GridView transportsView = findViewById(R.id.transportsView); setSupportActionBar(toolbar); ActionBar actionBar = requireNonNull(getSupportActionBar()); @@ -128,6 +149,9 @@ public class NavDrawerActivity extends BriarActivity implements drawerLayout.addDrawerListener(drawerToggle); navigation.setNavigationItemSelectedListener(this); + initializeTransports(); + transportsView.setAdapter(transportsAdapter); + lockManager.isLockable().observe(this, this::setLockVisible); if (lifecycleManager.getLifecycleState().isAfter(RUNNING)) { @@ -146,7 +170,7 @@ public class NavDrawerActivity extends BriarActivity implements public void onStart() { super.onStart(); lockManager.checkIfLockable(); - viewModel.checkExpiryWarning(); + navDrawerViewModel.checkExpiryWarning(); } @Override @@ -154,7 +178,7 @@ public class NavDrawerActivity extends BriarActivity implements @Nullable Intent data) { super.onActivityResult(request, result, data); if (request == REQUEST_PASSWORD && result == RESULT_OK) { - viewModel.checkDozeWhitelisting(); + navDrawerViewModel.checkDozeWhitelisting(); } } @@ -343,11 +367,101 @@ public class NavDrawerActivity extends BriarActivity implements ImageView expiryWarningClose = expiryWarning.findViewById(R.id.expiryWarningClose); expiryWarningClose.setOnClickListener(v -> - viewModel.expiryWarningDismissed() - ); + navDrawerViewModel.expiryWarningDismissed()); expiryWarning.setVisibility(VISIBLE); } else { expiryWarning.setVisibility(GONE); } } + + private void initializeTransports() { + transports = new ArrayList<>(3); + + transportsAdapter = new BaseAdapter() { + + @Override + public int getCount() { + return transports.size(); + } + + @Override + public Transport getItem(int position) { + return transports.get(position); + } + + @Override + public long getItemId(int position) { + return 0; + } + + @Override + public View getView(int position, View convertView, + ViewGroup parent) { + View view; + if (convertView != null) { + view = convertView; + } else { + LayoutInflater inflater = getLayoutInflater(); + view = inflater.inflate(R.layout.list_item_transport, + parent, false); + } + + Transport t = getItem(position); + + ImageView icon = view.findViewById(R.id.imageView); + icon.setImageDrawable(ContextCompat + .getDrawable(NavDrawerActivity.this, t.iconId)); + icon.setColorFilter(getIconColour(t.state)); + + TextView text = view.findViewById(R.id.textView); + text.setText(getString(t.textId)); + + return view; + } + }; + + transports.add(createTransport(TorConstants.ID, + R.drawable.transport_tor, R.string.transport_tor)); + transports.add(createTransport(LanTcpConstants.ID, + R.drawable.transport_lan, R.string.transport_lan)); + transports.add(createTransport(BluetoothConstants.ID, + R.drawable.transport_bt, R.string.transport_bt)); + } + + private int getIconColour(State state) { + int colorRes; + if (state == ACTIVE) { + colorRes = R.color.briar_green_light; + } else if (state == ENABLING) { + colorRes = R.color.briar_yellow; + } else { + colorRes = android.R.color.tertiary_text_light; + } + return ContextCompat.getColor(this, colorRes); + } + + private Transport createTransport(TransportId id, @DrawableRes int iconId, + @StringRes int textId) { + Transport transport = new Transport(iconId, textId); + pluginViewModel.getPluginState(id).observe(this, state -> { + transport.state = state; + transportsAdapter.notifyDataSetChanged(); + }); + return transport; + } + + private static class Transport { + + @DrawableRes + private final int iconId; + @StringRes + private final int textId; + + private State state = STARTING_STOPPING; + + private Transport(@DrawableRes int iconId, @StringRes int textId) { + this.iconId = iconId; + this.textId = textId; + } + } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerModule.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerModule.java index 966c178a2..e7282b1c7 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerModule.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerModule.java @@ -16,4 +16,8 @@ public abstract class NavDrawerModule { abstract ViewModel bindNavDrawerViewModel( NavDrawerViewModel navDrawerViewModel); + @Binds + @IntoMap + @ViewModelKey(PluginViewModel.class) + abstract ViewModel bindPluginViewModel(PluginViewModel pluginViewModel); } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerViewModel.java index 394f1b7c3..76c1aeb83 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerViewModel.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerViewModel.java @@ -4,54 +4,36 @@ import android.app.Application; import org.briarproject.bramble.api.db.DatabaseExecutor; import org.briarproject.bramble.api.db.DbException; -import org.briarproject.bramble.api.event.Event; -import org.briarproject.bramble.api.event.EventBus; -import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.plugin.BluetoothConstants; import org.briarproject.bramble.api.plugin.LanTcpConstants; -import org.briarproject.bramble.api.plugin.Plugin; -import org.briarproject.bramble.api.plugin.Plugin.State; -import org.briarproject.bramble.api.plugin.PluginManager; import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.plugin.TransportId; -import org.briarproject.bramble.api.plugin.event.TransportStateEvent; import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.SettingsManager; -import org.briarproject.bramble.api.system.LocationUtils; import java.util.concurrent.Executor; import java.util.logging.Logger; import javax.inject.Inject; -import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import static java.util.concurrent.TimeUnit.DAYS; -import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; import static java.util.logging.Logger.getLogger; -import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE; -import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; -import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE; -import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK; -import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WITH_BRIDGES; -import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING; import static org.briarproject.bramble.util.LogUtils.logException; import static org.briarproject.briar.android.TestingConstants.EXPIRY_DATE; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.controller.BriarControllerImpl.DOZE_ASK_AGAIN; import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE; -import static org.briarproject.briar.android.util.UiUtils.getCountryDisplayName; import static org.briarproject.briar.android.util.UiUtils.needsDozeWhitelisting; @NotNullByDefault -public class NavDrawerViewModel extends AndroidViewModel - implements EventListener { +public class NavDrawerViewModel extends AndroidViewModel { private static final Logger LOG = getLogger(NavDrawerViewModel.class.getName()); @@ -63,53 +45,18 @@ public class NavDrawerViewModel extends AndroidViewModel @DatabaseExecutor private final Executor dbExecutor; private final SettingsManager settingsManager; - private final PluginManager pluginManager; - private final LocationUtils locationUtils; - private final EventBus eventBus; private final MutableLiveData showExpiryWarning = new MutableLiveData<>(); private final MutableLiveData shouldAskForDozeWhitelisting = new MutableLiveData<>(); - private final MutableLiveData torPluginState = - new MutableLiveData<>(); - private final MutableLiveData wifiPluginState = - new MutableLiveData<>(); - private final MutableLiveData btPluginState = - new MutableLiveData<>(); - @Inject NavDrawerViewModel(Application app, @DatabaseExecutor Executor dbExecutor, - SettingsManager settingsManager, PluginManager pluginManager, - LocationUtils locationUtils, EventBus eventBus) { + SettingsManager settingsManager) { super(app); this.dbExecutor = dbExecutor; this.settingsManager = settingsManager; - this.pluginManager = pluginManager; - this.locationUtils = locationUtils; - this.eventBus = eventBus; - eventBus.addListener(this); - updatePluginStates(); - } - - @Override - protected void onCleared() { - eventBus.removeListener(this); - } - - @Override - public void eventOccurred(Event e) { - if (e instanceof TransportStateEvent) { - TransportStateEvent t = (TransportStateEvent) e; - TransportId id = t.getTransportId(); - State state = t.getState(); - if (LOG.isLoggable(INFO)) { - LOG.info("TransportStateEvent: " + id + " is " + state); - } - MutableLiveData liveData = getPluginLiveData(id); - if (liveData != null) liveData.postValue(state); - } } LiveData showExpiryWarning() { @@ -192,64 +139,4 @@ public class NavDrawerViewModel extends AndroidViewModel } }); } - - private void updatePluginStates() { - for (TransportId t : TRANSPORT_IDS) { - MutableLiveData liveData = getPluginLiveData(t); - if (liveData == null) throw new AssertionError(); - liveData.setValue(getTransportState(t)); - } - } - - private State getTransportState(TransportId id) { - Plugin plugin = pluginManager.getPlugin(id); - return plugin == null ? STARTING_STOPPING : plugin.getState(); - } - - @Nullable - private MutableLiveData getPluginLiveData(TransportId t) { - if (t.equals(TorConstants.ID)) { - return torPluginState; - } else if (t.equals(LanTcpConstants.ID)) { - return wifiPluginState; - } else if (t.equals(BluetoothConstants.ID)) { - return btPluginState; - } else { - return null; - } - } - - LiveData getPluginState(TransportId t) { - LiveData liveData = getPluginLiveData(t); - if (liveData == null) throw new AssertionError(); - return liveData; - } - - int getReasonsDisabled(TransportId id) { - Plugin plugin = pluginManager.getPlugin(id); - return plugin == null ? 0 : plugin.getReasonsDisabled(); - } - - void setPluginEnabled(TransportId t, boolean enabled) { - pluginManager.setPluginEnabled(t, enabled); - } - - void setTorEnabled(boolean battery, boolean mobileData, boolean location) { - Settings s = new Settings(); - s.putBoolean(PREF_PLUGIN_ENABLE, true); - if (battery) s.putBoolean(PREF_TOR_ONLY_WHEN_CHARGING, false); - if (mobileData) s.putBoolean(PREF_TOR_MOBILE, true); - if (location) s.putInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_WITH_BRIDGES); - dbExecutor.execute(() -> { - try { - settingsManager.mergeSettings(s, TorConstants.ID.getString()); - } catch (DbException e) { - logException(LOG, WARNING, e); - } - }); - } - - String getCurrentCountryName() { - return getCountryDisplayName(locationUtils.getCurrentCountry()); - } } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/PluginViewController.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/PluginViewController.java deleted file mode 100644 index 3f551b1ee..000000000 --- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/PluginViewController.java +++ /dev/null @@ -1,200 +0,0 @@ -package org.briarproject.briar.android.navdrawer; - -import android.view.View; -import android.widget.ImageView; -import android.widget.ScrollView; - -import org.briarproject.bramble.api.plugin.BluetoothConstants; -import org.briarproject.bramble.api.plugin.LanTcpConstants; -import org.briarproject.bramble.api.plugin.Plugin.State; -import org.briarproject.bramble.api.plugin.TorConstants; -import org.briarproject.bramble.api.plugin.TransportId; -import org.briarproject.briar.R; - -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.widget.AppCompatImageButton; -import androidx.appcompat.widget.SwitchCompat; -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.constraintlayout.widget.ConstraintSet; - -import static android.os.Build.VERSION.SDK_INT; -import static android.transition.TransitionManager.beginDelayedTransition; -import static android.view.View.FOCUS_DOWN; -import static androidx.core.content.ContextCompat.getColor; -import static org.briarproject.bramble.api.plugin.Plugin.REASON_USER; -import static org.briarproject.bramble.api.plugin.Plugin.State.ACTIVE; -import static org.briarproject.bramble.api.plugin.Plugin.State.DISABLED; -import static org.briarproject.bramble.api.plugin.Plugin.State.ENABLING; -import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; -import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY; -import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED; -import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA; -import static org.briarproject.briar.android.navdrawer.NavDrawerViewModel.TRANSPORT_IDS; -import static org.briarproject.briar.android.util.UiUtils.getDialogIcon; - -class PluginViewController { - - private final AppCompatActivity activity; - private final NavDrawerViewModel viewModel; - private final ConstraintLayout drawerContent; - private final ConstraintSet collapsedConstraints, expandedConstraints; - private final AppCompatImageButton chevronView; - private final ImageView torIcon, wifiIcon, btIcon; - private final SwitchCompat torSwitch, wifiSwitch, btSwitch; - - private boolean expanded = false; - - PluginViewController(View v, AppCompatActivity activity, - NavDrawerViewModel viewModel) { - this.activity = activity; - this.viewModel = viewModel; - drawerContent = v.findViewById(R.id.drawerContent); - - collapsedConstraints = new ConstraintSet(); - collapsedConstraints.clone(v.getContext(), - R.layout.navigation_menu_collapsed); - - expandedConstraints = new ConstraintSet(); - expandedConstraints.clone(v.getContext(), - R.layout.navigation_menu_expanded); - - // Scroll the drawer to the bottom when the view is expanded/collapsed - ScrollView scrollView = v.findViewById(R.id.drawerScrollView); - drawerContent.addOnLayoutChangeListener((view, left, top, right, - bottom, oldLeft, oldTop, oldRight, oldBottom) -> - scrollView.fullScroll(FOCUS_DOWN)); - - // Clicking the chevron expands or collapses the view - chevronView = v.findViewById(R.id.chevronView); - chevronView.setOnClickListener(view -> expandOrCollapseView()); - - // The whole view is clickable when collapsed - v.findViewById(R.id.connectionsBackground).setOnClickListener(view -> - expandOrCollapseView()); - - torIcon = v.findViewById(R.id.torIcon); - wifiIcon = v.findViewById(R.id.wifiIcon); - btIcon = v.findViewById(R.id.btIcon); - - torSwitch = v.findViewById(R.id.torSwitch); - wifiSwitch = v.findViewById(R.id.wifiSwitch); - btSwitch = v.findViewById(R.id.btSwitch); - - for (TransportId t : TRANSPORT_IDS) { - // a OnCheckedChangeListener would get triggered on programmatic updates - SwitchCompat switchCompat = getSwitch(t); - switchCompat.setOnClickListener(buttonView -> { - if (switchCompat.isChecked()) tryToEnablePlugin(t); - else viewModel.setPluginEnabled(t, false); - // Revert the switch to its previous state until the plugin - // changes its state - switchCompat.toggle(); - }); - viewModel.getPluginState(t).observe(activity, state -> - stateUpdate(t, state)); - } - } - - private void expandOrCollapseView() { - if (SDK_INT >= 19) beginDelayedTransition(drawerContent); - if (expanded) { - collapsedConstraints.applyTo(drawerContent); - chevronView.setImageResource(R.drawable.chevron_up_white); - } else { - expandedConstraints.applyTo(drawerContent); - chevronView.setImageResource(R.drawable.chevron_down_white); - } - expanded = !expanded; - } - - private void tryToEnablePlugin(TransportId id) { - if (id.equals(TorConstants.ID)) { - int reasons = viewModel.getReasonsDisabled(id); - if (reasons == 0 || reasons == REASON_USER) { - viewModel.setPluginEnabled(id, true); - } else { - showTorSettingsDialog(reasons); - } - } else { - viewModel.setPluginEnabled(id, true); - } - } - - private void stateUpdate(TransportId id, State state) { - updateIcon(getIcon(id), state); - updateSwitch(getSwitch(id), state); - } - - private SwitchCompat getSwitch(TransportId id) { - if (id == TorConstants.ID) return torSwitch; - if (id == BluetoothConstants.ID) return btSwitch; - if (id == LanTcpConstants.ID) return wifiSwitch; - throw new AssertionError(); - } - - private void updateSwitch(SwitchCompat switchCompat, State state) { - boolean checked = state != STARTING_STOPPING && state != DISABLED; - switchCompat.setChecked(checked); - switchCompat.setEnabled(state != STARTING_STOPPING); - } - - private ImageView getIcon(TransportId id) { - if (id == TorConstants.ID) return torIcon; - if (id == BluetoothConstants.ID) return btIcon; - if (id == LanTcpConstants.ID) return wifiIcon; - throw new AssertionError(); - } - - private void updateIcon(ImageView icon, State state) { - int colorRes; - if (state == ACTIVE) { - colorRes = R.color.briar_green_light; - } else if (state == ENABLING) { - colorRes = R.color.briar_yellow; - } else { - colorRes = android.R.color.tertiary_text_light; - } - int color = getColor(icon.getContext(), colorRes); - icon.setColorFilter(color); - } - - private void showTorSettingsDialog(int reasonsDisabled) { - boolean battery = (reasonsDisabled & REASON_BATTERY) != 0; - boolean mobileData = (reasonsDisabled & REASON_MOBILE_DATA) != 0; - boolean location = (reasonsDisabled & REASON_COUNTRY_BLOCKED) != 0; - - StringBuilder s = new StringBuilder(); - if (location) { - s.append("\t\u2022 "); - s.append(activity.getString(R.string.tor_override_network_setting, - viewModel.getCurrentCountryName())); - s.append('\n'); - } - if (mobileData) { - s.append("\t\u2022 "); - s.append(activity.getString( - R.string.tor_override_mobile_data_setting)); - s.append('\n'); - } - if (battery) { - s.append("\t\u2022 "); - s.append(activity.getString(R.string.tor_only_when_charging_title)); - s.append('\n'); - } - String message = activity.getString( - R.string.tor_override_settings_body, s.toString()); - - AlertDialog.Builder b = - new AlertDialog.Builder(activity, R.style.BriarDialogTheme); - b.setTitle(R.string.tor_override_settings_title); - b.setIcon(getDialogIcon(activity, R.drawable.ic_settings_black_24dp)); - b.setMessage(message); - b.setPositiveButton(R.string.tor_override_settings_confirm, - (dialog, which) -> - viewModel.setTorEnabled(battery, mobileData, location)); - b.setNegativeButton(R.string.cancel, (dialog, which) -> - dialog.dismiss()); - b.show(); - } -} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/PluginViewModel.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/PluginViewModel.java new file mode 100644 index 000000000..f67736ba4 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/PluginViewModel.java @@ -0,0 +1,215 @@ +package org.briarproject.briar.android.navdrawer; + +import android.app.Application; + +import org.briarproject.bramble.api.db.DatabaseExecutor; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.BluetoothConstants; +import org.briarproject.bramble.api.plugin.LanTcpConstants; +import org.briarproject.bramble.api.plugin.Plugin; +import org.briarproject.bramble.api.plugin.Plugin.State; +import org.briarproject.bramble.api.plugin.PluginManager; +import org.briarproject.bramble.api.plugin.TorConstants; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.plugin.event.TransportStateEvent; +import org.briarproject.bramble.api.settings.Settings; +import org.briarproject.bramble.api.settings.SettingsManager; +import org.briarproject.bramble.api.system.LocationUtils; +import org.briarproject.briar.R; + +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import androidx.appcompat.app.AlertDialog; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static java.util.logging.Logger.getLogger; +import static org.briarproject.bramble.api.plugin.Plugin.PREF_PLUGIN_ENABLE; +import static org.briarproject.bramble.api.plugin.Plugin.REASON_USER; +import static org.briarproject.bramble.api.plugin.Plugin.State.STARTING_STOPPING; +import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_MOBILE; +import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK; +import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_WITH_BRIDGES; +import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_ONLY_WHEN_CHARGING; +import static org.briarproject.bramble.api.plugin.TorConstants.REASON_BATTERY; +import static org.briarproject.bramble.api.plugin.TorConstants.REASON_COUNTRY_BLOCKED; +import static org.briarproject.bramble.api.plugin.TorConstants.REASON_MOBILE_DATA; +import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.briar.android.navdrawer.NavDrawerViewModel.TRANSPORT_IDS; +import static org.briarproject.briar.android.util.UiUtils.getCountryDisplayName; +import static org.briarproject.briar.android.util.UiUtils.getDialogIcon; + +@NotNullByDefault +public class PluginViewModel extends AndroidViewModel implements EventListener { + + private static final Logger LOG = + getLogger(PluginViewModel.class.getName()); + + private final Application app; + @DatabaseExecutor + private final Executor dbExecutor; + private final SettingsManager settingsManager; + private final PluginManager pluginManager; + private final LocationUtils locationUtils; + private final EventBus eventBus; + + private final MutableLiveData torPluginState = + new MutableLiveData<>(); + private final MutableLiveData wifiPluginState = + new MutableLiveData<>(); + private final MutableLiveData btPluginState = + new MutableLiveData<>(); + + @Inject + PluginViewModel(Application app, @DatabaseExecutor Executor dbExecutor, + SettingsManager settingsManager, PluginManager pluginManager, + LocationUtils locationUtils, EventBus eventBus) { + super(app); + this.app = app; + this.dbExecutor = dbExecutor; + this.settingsManager = settingsManager; + this.pluginManager = pluginManager; + this.locationUtils = locationUtils; + this.eventBus = eventBus; + eventBus.addListener(this); + initialisePluginStates(); + } + + @Override + protected void onCleared() { + eventBus.removeListener(this); + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof TransportStateEvent) { + TransportStateEvent t = (TransportStateEvent) e; + TransportId id = t.getTransportId(); + State state = t.getState(); + if (LOG.isLoggable(INFO)) { + LOG.info("TransportStateEvent: " + id + " is " + state); + } + MutableLiveData liveData = getPluginLiveData(id); + liveData.postValue(state); + } + } + + void onSwitchClicked(TransportId t, boolean isChecked) { + if (isChecked) tryToEnablePlugin(t); + else setPluginEnabled(t, false); + } + + LiveData getPluginState(TransportId t) { + return getPluginLiveData(t); + } + + private void tryToEnablePlugin(TransportId id) { + if (id.equals(TorConstants.ID)) { + int reasons = getReasonsDisabled(id); + if (reasons == 0 || reasons == REASON_USER) { + setPluginEnabled(id, true); + } else { + showTorSettingsDialog(reasons); + } + } else { + setPluginEnabled(id, true); + } + } + + private void initialisePluginStates() { + for (TransportId t : TRANSPORT_IDS) { + MutableLiveData liveData = getPluginLiveData(t); + liveData.setValue(getTransportState(t)); + } + } + + private State getTransportState(TransportId id) { + Plugin plugin = pluginManager.getPlugin(id); + return plugin == null ? STARTING_STOPPING : plugin.getState(); + } + + private MutableLiveData getPluginLiveData(TransportId t) { + if (t.equals(TorConstants.ID)) { + return torPluginState; + } else if (t.equals(LanTcpConstants.ID)) { + return wifiPluginState; + } else if (t.equals(BluetoothConstants.ID)) { + return btPluginState; + } else { + throw new IllegalArgumentException(); + } + } + + private int getReasonsDisabled(TransportId id) { + Plugin plugin = pluginManager.getPlugin(id); + return plugin == null ? 0 : plugin.getReasonsDisabled(); + } + + private void setPluginEnabled(TransportId t, boolean enabled) { + pluginManager.setPluginEnabled(t, enabled); + } + + private void setTorEnabled(boolean battery, boolean mobileData, + boolean location) { + Settings s = new Settings(); + s.putBoolean(PREF_PLUGIN_ENABLE, true); + if (battery) s.putBoolean(PREF_TOR_ONLY_WHEN_CHARGING, false); + if (mobileData) s.putBoolean(PREF_TOR_MOBILE, true); + if (location) s.putInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_WITH_BRIDGES); + dbExecutor.execute(() -> { + try { + settingsManager.mergeSettings(s, TorConstants.ID.getString()); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + private void showTorSettingsDialog(int reasonsDisabled) { + boolean battery = (reasonsDisabled & REASON_BATTERY) != 0; + boolean mobileData = (reasonsDisabled & REASON_MOBILE_DATA) != 0; + boolean location = (reasonsDisabled & REASON_COUNTRY_BLOCKED) != 0; + + StringBuilder s = new StringBuilder(); + if (location) { + s.append("\t\u2022 "); + s.append(app.getString(R.string.tor_override_network_setting, + getCountryDisplayName(locationUtils.getCurrentCountry()))); + s.append('\n'); + } + if (mobileData) { + s.append("\t\u2022 "); + s.append(app.getString(R.string.tor_override_mobile_data_setting)); + s.append('\n'); + } + if (battery) { + s.append("\t\u2022 "); + s.append(app.getString(R.string.tor_only_when_charging_title)); + s.append('\n'); + } + String message = app.getString( + R.string.tor_override_settings_body, s.toString()); + + AlertDialog.Builder b = + new AlertDialog.Builder(app, R.style.BriarDialogTheme); + b.setTitle(R.string.tor_override_settings_title); + b.setIcon(getDialogIcon(app, R.drawable.ic_settings_black_24dp)); + b.setMessage(message); + b.setPositiveButton(R.string.tor_override_settings_confirm, + (dialog, which) -> + setTorEnabled(battery, mobileData, location)); + b.setNegativeButton(R.string.cancel, (dialog, which) -> + dialog.dismiss()); + b.show(); + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java index 07dd5a327..79879580d 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java @@ -20,6 +20,8 @@ import org.briarproject.bramble.api.event.EventBus; import org.briarproject.bramble.api.event.EventListener; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.bramble.api.plugin.BluetoothConstants; +import org.briarproject.bramble.api.plugin.LanTcpConstants; import org.briarproject.bramble.api.plugin.TorConstants; import org.briarproject.bramble.api.settings.Settings; import org.briarproject.bramble.api.settings.SettingsManager; @@ -113,7 +115,15 @@ public class SettingsFragment extends PreferenceFragmentCompat "pref_key_lock_timeout"; public static final String NOTIFY_SIGN_IN = "pref_key_notify_sign_in"; + private static final String BT_NAMESPACE = + BluetoothConstants.ID.getString(); + private static final String BT_ENABLE = "pref_key_bluetooth"; + + private static final String WIFI_NAMESPACE = LanTcpConstants.ID.getString(); + private static final String WIFI_ENABLE = "pref_key_wifi"; + private static final String TOR_NAMESPACE = TorConstants.ID.getString(); + private static final String TOR_ENABLE = "pref_key_tor_enable"; private static final String TOR_NETWORK = "pref_key_tor_network"; private static final String TOR_MOBILE = "pref_key_tor_mobile_data"; private static final String TOR_ONLY_WHEN_CHARGING = @@ -124,6 +134,9 @@ public class SettingsFragment extends PreferenceFragmentCompat private SettingsActivity listener; private ListPreference language; + private SwitchPreference enableBluetooth; + private SwitchPreference enableWifi; + private SwitchPreference enableTor; private ListPreference torNetwork; private SwitchPreference torMobile; private SwitchPreference torOnlyWhenCharging; @@ -138,7 +151,7 @@ public class SettingsFragment extends PreferenceFragmentCompat private Preference notifySound; // Fields that are accessed from background threads must be volatile - private volatile Settings settings, torSettings; + private volatile Settings settings, btSettings, wifiSettings, torSettings; private volatile boolean settingsLoaded = false; @Inject @@ -167,6 +180,9 @@ public class SettingsFragment extends PreferenceFragmentCompat language = findPreference(LANGUAGE); setLanguageEntries(); ListPreference theme = findPreference("pref_key_theme"); + enableBluetooth = findPreference(BT_ENABLE); + enableWifi = findPreference(WIFI_ENABLE); + enableTor = findPreference(TOR_ENABLE); torNetwork = findPreference(TOR_NETWORK); torMobile = findPreference(TOR_MOBILE); torOnlyWhenCharging = findPreference(TOR_ONLY_WHEN_CHARGING); @@ -198,6 +214,9 @@ public class SettingsFragment extends PreferenceFragmentCompat } return true; }); + enableBluetooth.setOnPreferenceChangeListener(this); + enableWifi.setOnPreferenceChangeListener(this); + enableTor.setOnPreferenceChangeListener(this); torNetwork.setOnPreferenceChangeListener(this); torMobile.setOnPreferenceChangeListener(this); torOnlyWhenCharging.setOnPreferenceChangeListener(this); @@ -339,8 +358,9 @@ public class SettingsFragment extends PreferenceFragmentCompat try { long start = now(); settings = settingsManager.getSettings(SETTINGS_NAMESPACE); - torSettings = migrateTorSettings( - settingsManager.getSettings(TOR_NAMESPACE)); + btSettings = settingsManager.getSettings(BT_NAMESPACE); + wifiSettings = settingsManager.getSettings(WIFI_NAMESPACE); + torSettings = settingsManager.getSettings(TOR_NAMESPACE); settingsLoaded = true; logDuration(LOG, "Loading settings", start); displaySettings(); @@ -368,6 +388,18 @@ public class SettingsFragment extends PreferenceFragmentCompat // due to events, we might try to display before a load completed if (!settingsLoaded) return; + boolean btEnabledSetting = + btSettings.getBoolean(PREF_PLUGIN_ENABLE, false); + enableBluetooth.setChecked(btEnabledSetting); + + boolean wifiEnabledSetting = + wifiSettings.getBoolean(PREF_PLUGIN_ENABLE, false); + enableWifi.setChecked(wifiEnabledSetting); + + boolean torEnabledSetting = + torSettings.getBoolean(PREF_PLUGIN_ENABLE, true); + enableTor.setChecked(torEnabledSetting); + int torNetworkSetting = torSettings.getInt(PREF_TOR_NETWORK, PREF_TOR_NETWORK_AUTOMATIC); torNetwork.setValue(Integer.toString(torNetworkSetting)); @@ -439,6 +471,9 @@ public class SettingsFragment extends PreferenceFragmentCompat // preferences partly needed here, because they have their own logic // - pref_key_lock (screenLock -> displayScreenLockSetting()) // - pref_key_lock_timeout (screenLockTimeout) + enableBluetooth.setEnabled(enabled); + enableWifi.setEnabled(enabled); + enableTor.setEnabled(enabled); torNetwork.setEnabled(enabled); torMobile.setEnabled(enabled); torOnlyWhenCharging.setEnabled(enabled); @@ -540,6 +575,15 @@ public class SettingsFragment extends PreferenceFragmentCompat if (!language.getValue().equals(newValue)) languageChanged((String) newValue); return false; + } else if (preference == enableBluetooth) { + boolean btSetting = (Boolean) newValue; + storeBluetoothSetting(btSetting); + } else if (preference == enableWifi) { + boolean wifiSetting = (Boolean) newValue; + storeWifiSetting(wifiSetting); + } else if (preference == enableTor) { + boolean torEnabledSetting = (Boolean) newValue; + storeTorEnabledSetting(torEnabledSetting); } else if (preference == torNetwork) { int torNetworkSetting = Integer.valueOf((String) newValue); storeTorNetworkSetting(torNetworkSetting); @@ -603,6 +647,12 @@ public class SettingsFragment extends PreferenceFragmentCompat builder.show(); } + private void storeTorEnabledSetting(boolean torEnabledSetting) { + Settings s = new Settings(); + s.putBoolean(PREF_PLUGIN_ENABLE, torEnabledSetting); + mergeSettings(s, TOR_NAMESPACE); + } + private void storeTorNetworkSetting(int torNetworkSetting) { Settings s = new Settings(); s.putInt(PREF_TOR_NETWORK, torNetworkSetting); @@ -621,6 +671,18 @@ public class SettingsFragment extends PreferenceFragmentCompat mergeSettings(s, TOR_NAMESPACE); } + private void storeBluetoothSetting(boolean btSetting) { + Settings s = new Settings(); + s.putBoolean(PREF_PLUGIN_ENABLE, btSetting); + mergeSettings(s, BT_NAMESPACE); + } + + private void storeWifiSetting(boolean wifiSetting) { + Settings s = new Settings(); + s.putBoolean(PREF_PLUGIN_ENABLE, wifiSetting); + mergeSettings(s, WIFI_NAMESPACE); + } + private void storeSettings(Settings s) { mergeSettings(s, SETTINGS_NAMESPACE); } @@ -679,6 +741,14 @@ public class SettingsFragment extends PreferenceFragmentCompat LOG.info("Settings updated"); settings = s.getSettings(); displaySettings(); + } else if (namespace.equals(BT_NAMESPACE)) { + LOG.info("Bluetooth settings updated"); + btSettings = s.getSettings(); + displaySettings(); + } else if (namespace.equals(WIFI_NAMESPACE)) { + LOG.info("Wifi settings updated"); + wifiSettings = s.getSettings(); + displaySettings(); } else if (namespace.equals(TOR_NAMESPACE)) { LOG.info("Tor settings updated"); torSettings = migrateTorSettings(s.getSettings()); diff --git a/briar-android/src/main/res/drawable/ic_baseline_drag_handle.xml b/briar-android/src/main/res/drawable/ic_baseline_drag_handle.xml deleted file mode 100644 index de7bbda0a..000000000 --- a/briar-android/src/main/res/drawable/ic_baseline_drag_handle.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - diff --git a/briar-android/src/main/res/layout/navigation_menu.xml b/briar-android/src/main/res/layout/navigation_menu.xml index f23d4bda9..4a7a32c4f 100644 --- a/briar-android/src/main/res/layout/navigation_menu.xml +++ b/briar-android/src/main/res/layout/navigation_menu.xml @@ -1,12 +1,59 @@ - + - \ No newline at end of file + + + + + + + + + + + diff --git a/briar-android/src/main/res/layout/navigation_menu_collapsed.xml b/briar-android/src/main/res/layout/navigation_menu_collapsed.xml deleted file mode 100644 index 11daa10fc..000000000 --- a/briar-android/src/main/res/layout/navigation_menu_collapsed.xml +++ /dev/null @@ -1,165 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/briar-android/src/main/res/layout/navigation_menu_expanded.xml b/briar-android/src/main/res/layout/navigation_menu_expanded.xml deleted file mode 100644 index 1573a9917..000000000 --- a/briar-android/src/main/res/layout/navigation_menu_expanded.xml +++ /dev/null @@ -1,183 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/briar-android/src/main/res/layout/transports_list.xml b/briar-android/src/main/res/layout/transports_list.xml new file mode 100644 index 000000000..a484522c7 --- /dev/null +++ b/briar-android/src/main/res/layout/transports_list.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index a67955b1f..ef8a56111 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -70,10 +70,7 @@ Sign Out - Connections - Internet - Tor - Nearby + Internet Bluetooth Wi-Fi @@ -440,15 +437,19 @@ Connections - Connection method for Internet (Tor) + Connect to contacts via Bluetooth + Connect to contacts on the same Wi-Fi network + Connect to contacts via the Internet + All connections go through the Tor network for privacy + Connection method for Tor network Automatic based on location - Use Tor without bridges - Use Tor with bridges - Don\'t connect - + Use Tor network without bridges + Use Tor network with bridges + Don\'t connect to the Internet + Automatic: %1$s (in %2$s) Use mobile data - Connect via Internet (Tor) only when charging + Connect to the Internet only when charging Disables Internet connection when device is running on battery @@ -571,9 +572,9 @@ Change Settings - Turning on Tor will change the following settings:\n\n%1$s + Turning on connections via the Internet will change the following settings:\n\n%1$s Don\'t use mobile data - Don\'t connect to Internet (Tor) in %1$s + Don\'t connect to the Internet in %1$s Change diff --git a/briar-android/src/main/res/xml/settings.xml b/briar-android/src/main/res/xml/settings.xml index 3a5781a8d..21acdd98f 100644 --- a/briar-android/src/main/res/xml/settings.xml +++ b/briar-android/src/main/res/xml/settings.xml @@ -29,8 +29,34 @@ android:layout="@layout/preferences_category" android:title="@string/network_settings_title"> + + + + + +