diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java index 7203aefa5..e5e7aabc0 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java @@ -82,7 +82,7 @@ import rx.schedulers.Schedulers; */ public class SwapService extends Service { - private static final String TAG = "SwapManager"; + private static final String TAG = "SwapService"; public static final String SHARED_PREFERENCES = "swap-state"; private static final String KEY_APPS_TO_SWAP = "appsToSwap"; private static final String KEY_BLUETOOTH_ENABLED = "bluetoothEnabled"; diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java index 2923865cf..64e2004e7 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java @@ -102,7 +102,11 @@ public class StartSwapView extends ScrollView implements SwapWorkflowActivity.In @Nullable /* Emulators typically don't have bluetooth adapters */ private final BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter(); + private SwitchCompat wifiSwitch; + private SwitchCompat bluetoothSwitch; + private TextView textWifiVisible; private TextView viewBluetoothId; + private TextView textBluetoothVisible; private TextView viewWifiId; private TextView viewWifiNetwork; private TextView peopleNearbyText; @@ -136,7 +140,12 @@ public class StartSwapView extends ScrollView implements SwapWorkflowActivity.In private Subscription peerFinderSubscription; - // TODO: Not sure if this is the best place to handle being removed from the view. + /** + * Remove relevant listeners/subscriptions/etc so that they do not receive and process events + * when this view is not in use. + * + * TODO: Not sure if this is the best place to handle being removed from the view. + */ @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); @@ -144,6 +153,18 @@ public class StartSwapView extends ScrollView implements SwapWorkflowActivity.In peerFinderSubscription.unsubscribe(); peerFinderSubscription = null; } + + if (wifiSwitch != null) { + wifiSwitch.setOnCheckedChangeListener(null); + } + + if (bluetoothSwitch != null) { + bluetoothSwitch.setOnCheckedChangeListener(null); + } + + LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(onWifiSwapStateChanged); + LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(onBluetoothSwapStateChanged); + LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(onWifiNetworkChanged); } @Override @@ -160,18 +181,17 @@ public class StartSwapView extends ScrollView implements SwapWorkflowActivity.In uiInitButtons(); uiShowSearchingForPeers(); - // TODO: Unregister this receiver at some point. LocalBroadcastManager.getInstance(getActivity()).registerReceiver( - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - uiUpdateWifiNetwork(); - } - }, - new IntentFilter(WifiStateChangeService.BROADCAST) - ); + onWifiNetworkChanged, new IntentFilter(WifiStateChangeService.BROADCAST)); } + private BroadcastReceiver onWifiNetworkChanged = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + uiUpdateWifiNetwork(); + } + }; + private void uiInitButtons() { findViewById(R.id.btn_send_fdroid).setOnClickListener(new OnClickListener() { @Override @@ -227,7 +247,7 @@ public class StartSwapView extends ScrollView implements SwapWorkflowActivity.In private void uiInitBluetooth() { if (bluetooth != null) { - final TextView textBluetoothVisible = (TextView) findViewById(R.id.bluetooth_visible); + textBluetoothVisible = (TextView) findViewById(R.id.bluetooth_visible); viewBluetoothId = (TextView) findViewById(R.id.device_id_bluetooth); viewBluetoothId.setText(bluetooth.getName()); @@ -236,80 +256,87 @@ public class StartSwapView extends ScrollView implements SwapWorkflowActivity.In int textResource = getManager().isBluetoothDiscoverable() ? R.string.swap_visible_bluetooth : R.string.swap_not_visible_bluetooth; textBluetoothVisible.setText(textResource); - final SwitchCompat bluetoothSwitch = (SwitchCompat) findViewById(R.id.switch_bluetooth); + bluetoothSwitch = (SwitchCompat) findViewById(R.id.switch_bluetooth); Utils.debugLog(TAG, getManager().isBluetoothDiscoverable() ? "Initially marking switch as checked, because Bluetooth is discoverable." : "Initially marking switch as not-checked, because Bluetooth is not discoverable."); - bluetoothSwitch.setChecked(getManager().isBluetoothDiscoverable()); - bluetoothSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked) { - Utils.debugLog(TAG, "Received onCheckChanged(true) for Bluetooth swap, prompting user as to whether they want to enable Bluetooth."); - getActivity().startBluetoothSwap(); - textBluetoothVisible.setText(R.string.swap_visible_bluetooth); - viewBluetoothId.setVisibility(View.VISIBLE); - Utils.debugLog(TAG, "Received onCheckChanged(true) for Bluetooth swap (prompting user or setup Bluetooth complete)"); - // TODO: When they deny the request for enabling bluetooth, we need to disable this switch... - } else { - Utils.debugLog(TAG, "Received onCheckChanged(false) for Bluetooth swap, disabling Bluetooth swap."); - getManager().getBluetoothSwap().stop(); - textBluetoothVisible.setText(R.string.swap_not_visible_bluetooth); - viewBluetoothId.setVisibility(View.GONE); - Utils.debugLog(TAG, "Received onCheckChanged(false) for Bluetooth swap, Bluetooth swap disabled successfully."); - } - } - }); + bluetoothSwitch.setOnCheckedChangeListener(onBluetoothSwitchToggled); + setBluetoothSwitchState(getManager().isBluetoothDiscoverable(), true); - // TODO: Unregister receiver correctly... - LocalBroadcastManager.getInstance(getContext()).registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.hasExtra(SwapService.EXTRA_STARTING)) { - Utils.debugLog(TAG, "Bluetooth service is starting (setting toggle to disabled, not checking because we will wait for an intent that bluetooth is actually enabled)"); - bluetoothSwitch.setEnabled(false); - textBluetoothVisible.setText(R.string.swap_setting_up_bluetooth); - // bluetoothSwitch.setChecked(true); - } else { - bluetoothSwitch.setEnabled(true); - if (intent.hasExtra(SwapService.EXTRA_STARTED)) { - Utils.debugLog(TAG, "Bluetooth service has started (updating text to visible, but not marking as checked)."); - textBluetoothVisible.setText(R.string.swap_visible_bluetooth); - // bluetoothSwitch.setChecked(true); - } else { - Utils.debugLog(TAG, "Bluetooth service has stopped (setting switch to not-visible)."); - textBluetoothVisible.setText(R.string.swap_not_visible_bluetooth); - bluetoothSwitch.setChecked(false); - } - } - } - }, new IntentFilter(SwapService.BLUETOOTH_STATE_CHANGE)); + LocalBroadcastManager.getInstance(getContext()).registerReceiver(onBluetoothSwapStateChanged, new IntentFilter(SwapService.BLUETOOTH_STATE_CHANGE)); } else { findViewById(R.id.bluetooth_info).setVisibility(View.GONE); } } + /** + * @see StartSwapView#onWifiSwapStateChanged + */ + private BroadcastReceiver onBluetoothSwapStateChanged = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.hasExtra(SwapService.EXTRA_STARTING)) { + Utils.debugLog(TAG, "Bluetooth service is starting (setting toggle to disabled, not checking because we will wait for an intent that bluetooth is actually enabled)"); + bluetoothSwitch.setEnabled(false); + textBluetoothVisible.setText(R.string.swap_setting_up_bluetooth); + // bluetoothSwitch.setChecked(true); + } else { + if (intent.hasExtra(SwapService.EXTRA_STARTED)) { + Utils.debugLog(TAG, "Bluetooth service has started (updating text to visible, but not marking as checked)."); + textBluetoothVisible.setText(R.string.swap_visible_bluetooth); + bluetoothSwitch.setEnabled(true); + // bluetoothSwitch.setChecked(true); + } else { + Utils.debugLog(TAG, "Bluetooth service has stopped (setting switch to not-visible)."); + textBluetoothVisible.setText(R.string.swap_not_visible_bluetooth); + setBluetoothSwitchState(false, true); + } + } + } + }; + + /** + * @see StartSwapView#setWifiSwitchState(boolean, boolean) + */ + private void setBluetoothSwitchState(boolean isChecked, boolean isEnabled) { + bluetoothSwitch.setOnCheckedChangeListener(null); + bluetoothSwitch.setChecked(isChecked); + bluetoothSwitch.setEnabled(isEnabled); + bluetoothSwitch.setOnCheckedChangeListener(onBluetoothSwitchToggled); + } + + /** + * @see StartSwapView#onWifiSwitchToggled + */ + private final CompoundButton.OnCheckedChangeListener onBluetoothSwitchToggled = new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + Utils.debugLog(TAG, "Received onCheckChanged(true) for Bluetooth swap, prompting user as to whether they want to enable Bluetooth."); + getActivity().startBluetoothSwap(); + textBluetoothVisible.setText(R.string.swap_visible_bluetooth); + viewBluetoothId.setVisibility(View.VISIBLE); + Utils.debugLog(TAG, "Received onCheckChanged(true) for Bluetooth swap (prompting user or setup Bluetooth complete)"); + // TODO: When they deny the request for enabling bluetooth, we need to disable this switch... + } else { + Utils.debugLog(TAG, "Received onCheckChanged(false) for Bluetooth swap, disabling Bluetooth swap."); + getManager().getBluetoothSwap().stop(); + textBluetoothVisible.setText(R.string.swap_not_visible_bluetooth); + viewBluetoothId.setVisibility(View.GONE); + Utils.debugLog(TAG, "Received onCheckChanged(false) for Bluetooth swap, Bluetooth swap disabled successfully."); + } + } + }; + private void uiInitWifi() { viewWifiId = (TextView) findViewById(R.id.device_id_wifi); viewWifiNetwork = (TextView) findViewById(R.id.wifi_network); - final SwitchCompat wifiSwitch = (SwitchCompat) findViewById(R.id.switch_wifi); - wifiSwitch.setChecked(getManager().isBonjourDiscoverable()); - wifiSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked) { - Utils.debugLog(TAG, "Received onCheckChanged(true) for WiFi swap, asking in background thread to ensure WiFi swap is running."); - getManager().getWifiSwap().ensureRunningInBackground(); - } else { - Utils.debugLog(TAG, "Received onCheckChanged(false) for WiFi swap, disabling WiFi swap in background thread."); - getManager().getWifiSwap().stopInBackground(); - } - uiUpdateWifiNetwork(); - } - }); + wifiSwitch = (SwitchCompat) findViewById(R.id.switch_wifi); + wifiSwitch.setOnCheckedChangeListener(onWifiSwitchToggled); + setWifiSwitchState(getManager().isBonjourDiscoverable(), true); - final TextView textWifiVisible = (TextView) findViewById(R.id.wifi_visible); + textWifiVisible = (TextView) findViewById(R.id.wifi_visible); int textResource = getManager().isBonjourDiscoverable() ? R.string.swap_visible_wifi : R.string.swap_not_visible_wifi; textWifiVisible.setText(textResource); @@ -317,35 +344,7 @@ public class StartSwapView extends ScrollView implements SwapWorkflowActivity.In // and the Bonjour service at the same time. Technically swap will work fine without // Bonjour, and that is more of a convenience. Thus, we should show feedback once wifi // is ready, even if Bonjour is not yet. - // TODO: Unregister receiver correctly... - LocalBroadcastManager.getInstance(getContext()).registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent.hasExtra(SwapService.EXTRA_STARTING)) { - Utils.debugLog(TAG, "WiFi service is starting (setting toggle to checked, but disabled)."); - textWifiVisible.setText(R.string.swap_setting_up_wifi); - wifiSwitch.setEnabled(false); - wifiSwitch.setChecked(true); - } else if (intent.hasExtra(SwapService.EXTRA_STOPPING)) { - Utils.debugLog(TAG, "WiFi service is stopping (setting toggle to unchecked and disabled)."); - textWifiVisible.setText(R.string.swap_stopping_wifi); - wifiSwitch.setEnabled(false); - wifiSwitch.setChecked(false); - } else { - wifiSwitch.setEnabled(true); - if (intent.hasExtra(SwapService.EXTRA_STARTED)) { - Utils.debugLog(TAG, "WiFi service has started (setting toggle to visible)."); - textWifiVisible.setText(R.string.swap_visible_wifi); - wifiSwitch.setChecked(true); - } else { - Utils.debugLog(TAG, "WiFi service has stopped (setting toggle to not-visible)."); - textWifiVisible.setText(R.string.swap_not_visible_wifi); - wifiSwitch.setChecked(false); - } - } - uiUpdateWifiNetwork(); - } - }, new IntentFilter(SwapService.WIFI_STATE_CHANGE)); + LocalBroadcastManager.getInstance(getContext()).registerReceiver(onWifiSwapStateChanged, new IntentFilter(SwapService.WIFI_STATE_CHANGE)); viewWifiNetwork.setOnClickListener(new OnClickListener() { @Override @@ -357,6 +356,80 @@ public class StartSwapView extends ScrollView implements SwapWorkflowActivity.In uiUpdateWifiNetwork(); } + /** + * When the WiFi swap service is started or stopped, update the UI appropriately. + * This includes both the in-transit states of "Starting" and "Stopping". In these two cases, + * the UI should be disabled to prevent the user quickly switching back and forth - causing + * multiple start/stop actions to be sent to the swap service. + */ + private final BroadcastReceiver onWifiSwapStateChanged = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.hasExtra(SwapService.EXTRA_STARTING)) { + Utils.debugLog(TAG, "WiFi service is starting (setting toggle to checked, but disabled)."); + textWifiVisible.setText(R.string.swap_setting_up_wifi); + setWifiSwitchState(true, false); + } else if (intent.hasExtra(SwapService.EXTRA_STOPPING)) { + Utils.debugLog(TAG, "WiFi service is stopping (setting toggle to unchecked and disabled)."); + textWifiVisible.setText(R.string.swap_stopping_wifi); + setWifiSwitchState(false, false); + } else { + if (intent.hasExtra(SwapService.EXTRA_STARTED)) { + Utils.debugLog(TAG, "WiFi service has started (setting toggle to visible)."); + textWifiVisible.setText(R.string.swap_visible_wifi); + setWifiSwitchState(true, true); + } else { + Utils.debugLog(TAG, "WiFi service has stopped (setting toggle to not-visible)."); + textWifiVisible.setText(R.string.swap_not_visible_wifi); + setWifiSwitchState(false, true); + } + } + uiUpdateWifiNetwork(); + } + }; + + /** + * Helper function to set the "enable wifi" switch, but prevents the listeners from + * being notified. This enables the UI to be updated without triggering further enable/disable + * events being queued. + * + * This is required because the SwitchCompat and its parent classes will always try to notify + * their listeners if there is one (e.g. http://stackoverflow.com/a/15523518). + * + * The fact that this method also deals with enabling/disabling the switch is more of a convenience + * Nigh on all times this UI wants to change the state of the switch, it is also interested in + * ensuring the enabled state of the switch. + */ + private void setWifiSwitchState(boolean isChecked, boolean isEnabled) { + wifiSwitch.setOnCheckedChangeListener(null); + wifiSwitch.setChecked(isChecked); + wifiSwitch.setEnabled(isEnabled); + wifiSwitch.setOnCheckedChangeListener(onWifiSwitchToggled); + } + + /** + * When the wifi switch is: + * + * Toggled on: Ask the swap service to ensure wifi swap is running. + * Toggled off: Ask the swap service to prevent the wifi swap service from running. + * + * Both of these actions will be performed in a background thread which will send broadcast + * intents when they are completed. + */ + private final CompoundButton.OnCheckedChangeListener onWifiSwitchToggled = new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + Utils.debugLog(TAG, "Received onCheckChanged(true) for WiFi swap, asking in background thread to ensure WiFi swap is running."); + getManager().getWifiSwap().ensureRunningInBackground(); + } else { + Utils.debugLog(TAG, "Received onCheckChanged(false) for WiFi swap, disabling WiFi swap in background thread."); + getManager().getWifiSwap().stopInBackground(); + } + uiUpdateWifiNetwork(); + } + }; + private void uiUpdateWifiNetwork() { viewWifiId.setText(FDroidApp.ipAddressString);