replace BluetoothSwap with BluetoothManager, using HandlerThread

This is the last of the SwapType stuff!

fdroid/fdroidclient#1723
fdroid/fdroidclient#1669
This commit is contained in:
Hans-Christoph Steiner 2019-05-22 16:21:54 +02:00
parent ddbe93aeb4
commit fea778793c
12 changed files with 319 additions and 405 deletions

View File

@ -0,0 +1,135 @@
package org.fdroid.fdroid.localrepo;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Process;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.net.bluetooth.BluetoothServer;
import java.lang.ref.WeakReference;
/**
* Manage the {@link android.bluetooth.BluetoothAdapter}in a {@link HandlerThread}.
* The start process is in {@link HandlerThread#onLooperPrepared()} so that it is
* always started before any messages get delivered from the queue.
*
* @see BonjourManager
* @see LocalRepoManager
*/
public class BluetoothManager {
private static final String TAG = "BluetoothManager";
public static final String ACTION_STATUS = "BluetoothStatus";
public static final String EXTRA_STATUS = "BluetoothStatusExtra";
public static final int STATUS_STARTING = 0;
public static final int STATUS_STARTED = 1;
public static final int STATUS_STOPPING = 2;
public static final int STATUS_STOPPED = 3;
public static final int STATUS_ERROR = 0xffff;
private static final int STOP = 5709;
private static WeakReference<Context> context;
private static Handler handler;
private static volatile HandlerThread handlerThread;
private static BluetoothAdapter bluetoothAdapter;
/**
* Stops the Bluetooth adapter, triggering a status broadcast via {@link #ACTION_STATUS}.
* {@link #STATUS_STOPPED} can be broadcast multiple times for the same session,
* so make sure {@link android.content.BroadcastReceiver}s handle duplicates.
*/
public static void stop(Context context) {
BluetoothManager.context = new WeakReference<>(context);
if (handler == null || handlerThread == null || !handlerThread.isAlive()) {
Log.w(TAG, "handlerThread is already stopped, doing nothing!");
sendBroadcast(STATUS_STOPPED, null);
return;
}
sendBroadcast(STATUS_STOPPING, null);
handler.sendEmptyMessage(STOP);
}
/**
* Starts the service, triggering a status broadcast via {@link #ACTION_STATUS}.
* {@link #STATUS_STARTED} can be broadcast multiple times for the same session,
* so make sure {@link android.content.BroadcastReceiver}s handle duplicates.
*/
public static void start(final Context context) {
BluetoothManager.context = new WeakReference<>(context);
if (handlerThread != null && handlerThread.isAlive()) {
sendBroadcast(STATUS_STARTED, null);
return;
}
sendBroadcast(STATUS_STARTING, null);
final BluetoothServer bluetoothServer = new BluetoothServer(context.getFilesDir());
handlerThread = new HandlerThread("BluetoothManager", Process.THREAD_PRIORITY_LESS_FAVORABLE) {
@Override
protected void onLooperPrepared() {
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (!bluetoothAdapter.enable()) {
sendBroadcast(STATUS_ERROR, context.getString(R.string.swap_error_cannot_start_bluetooth));
return;
}
bluetoothServer.start();
if (bluetoothAdapter.startDiscovery()) {
sendBroadcast(STATUS_STARTED, null);
} else {
sendBroadcast(STATUS_ERROR, context.getString(R.string.swap_error_cannot_start_bluetooth));
}
}
};
handlerThread.start();
handler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
bluetoothServer.close();
if (bluetoothAdapter != null) {
bluetoothAdapter.cancelDiscovery();
if (!SwapService.wasBluetoothEnabledBeforeSwap()) {
bluetoothAdapter.disable();
}
}
handlerThread.quit();
handlerThread = null;
sendBroadcast(STATUS_STOPPED, null);
}
};
}
public static void restart(Context context) {
stop(context);
try {
handlerThread.join(10000);
} catch (InterruptedException | NullPointerException e) {
// ignored
}
start(context);
}
public static void setName(Context context, String name) {
// TODO
}
public static boolean isAlive() {
return handlerThread != null && handlerThread.isAlive();
}
private static void sendBroadcast(int status, String message) {
Intent intent = new Intent(ACTION_STATUS);
intent.putExtra(EXTRA_STATUS, status);
if (!TextUtils.isEmpty(message)) {
intent.putExtra(Intent.EXTRA_TEXT, message);
}
LocalBroadcastManager.getInstance(context.get()).sendBroadcast(intent);
}
}

View File

@ -32,7 +32,6 @@ import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.data.Schema;
import org.fdroid.fdroid.localrepo.peers.Peer;
import org.fdroid.fdroid.localrepo.peers.PeerFinder;
import org.fdroid.fdroid.localrepo.type.BluetoothSwap;
import org.fdroid.fdroid.net.Downloader;
import org.fdroid.fdroid.net.WifiStateChangeService;
import org.fdroid.fdroid.views.swap.SwapWorkflowActivity;
@ -91,10 +90,9 @@ public class SwapService extends Service {
context.stopService(intent);
}
// ==========================================================
// Search for peers to swap
// ==========================================================
/**
* Search for peers to swap
*/
private Observable<Peer> peerFinder;
/**
@ -132,7 +130,7 @@ public class SwapService extends Service {
throw new IllegalStateException("Cannot connect to peer, no peer has been selected.");
}
connectTo(getPeer());
if (isEnabled() && getPeer().shouldPromptForSwapBack()) {
if (LocalHTTPDManager.isAlive() && getPeer().shouldPromptForSwapBack()) {
askServerToSwapWithUs(peerRepo);
}
}
@ -334,32 +332,9 @@ public class SwapService extends Service {
swapPreferences.edit().putBoolean(SwapService.KEY_WIFI_ENABLED_BEFORE_SWAP, visible).apply();
}
public boolean isEnabled() {
return bluetoothSwap.isConnected() || LocalHTTPDManager.isAlive();
}
// ==========================================
// Interacting with Bluetooth adapter
// ==========================================
public boolean isBluetoothDiscoverable() {
return bluetoothSwap.isDiscoverable();
}
// ===============================================================
// Old SwapService stuff being merged into that.
// ===============================================================
public static final String BLUETOOTH_STATE_CHANGE = "org.fdroid.fdroid.BLUETOOTH_STATE_CHANGE";
public static final String EXTRA_STARTING = "STARTING";
public static final String EXTRA_STARTED = "STARTED";
public static final String EXTRA_STOPPING = "STOPPING";
public static final String EXTRA_STOPPED = "STOPPED";
private static final int NOTIFICATION = 1;
private final Binder binder = new Binder();
private BluetoothSwap bluetoothSwap;
private static final int TIMEOUT = 15 * 60 * 1000; // 15 mins
@ -369,10 +344,6 @@ public class SwapService extends Service {
@Nullable
private Timer timer;
public BluetoothSwap getBluetoothSwap() {
return bluetoothSwap;
}
public class Binder extends android.os.Binder {
public SwapService getService() {
return SwapService.this;
@ -390,6 +361,11 @@ public class SwapService extends Service {
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter != null) {
SwapService.putBluetoothEnabledBeforeSwap(bluetoothAdapter.isEnabled());
if (bluetoothAdapter.isEnabled()) {
BluetoothManager.start(this);
}
registerReceiver(bluetoothScanModeChanged,
new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED));
}
wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
@ -398,21 +374,12 @@ public class SwapService extends Service {
}
appsToSwap.addAll(deserializePackages(swapPreferences.getString(KEY_APPS_TO_SWAP, "")));
bluetoothSwap = BluetoothSwap.create(this);
Preferences.get().registerLocalRepoHttpsListeners(httpsEnabledListener);
localBroadcastManager.registerReceiver(onWifiChange,
new IntentFilter(WifiStateChangeService.BROADCAST));
localBroadcastManager.registerReceiver(onBluetoothSwapStateChange,
new IntentFilter(SwapService.BLUETOOTH_STATE_CHANGE));
if (getBluetoothVisibleUserPreference()) {
Utils.debugLog(TAG, "Previously the user enabled Bluetooth swap, so enabling again automatically.");
bluetoothSwap.startInBackground(); // TODO replace with Intent to SwapService
} else {
Utils.debugLog(TAG, "Bluetooth was NOT enabled last time user swapped, starting not visible.");
}
localBroadcastManager.registerReceiver(onWifiChange, new IntentFilter(WifiStateChangeService.BROADCAST));
localBroadcastManager.registerReceiver(bluetoothStatus, new IntentFilter(BluetoothManager.ACTION_STATUS));
localBroadcastManager.registerReceiver(localRepoStatus, new IntentFilter(LocalRepoService.ACTION_STATUS));
BonjourManager.start(this);
BonjourManager.setVisible(this, getWifiVisibleUserPreference());
@ -442,11 +409,11 @@ public class SwapService extends Service {
Utils.debugLog(TAG, "Destroying service, will disable swapping if required, and unregister listeners.");
Preferences.get().unregisterLocalRepoHttpsListeners(httpsEnabledListener);
localBroadcastManager.unregisterReceiver(onWifiChange);
localBroadcastManager.unregisterReceiver(onBluetoothSwapStateChange);
localBroadcastManager.unregisterReceiver(bluetoothStatus);
if (bluetoothAdapter != null && !wasBluetoothEnabledBeforeSwap()) {
bluetoothAdapter.disable();
}
unregisterReceiver(bluetoothScanModeChanged);
BluetoothManager.stop(this);
BonjourManager.stop(this);
LocalHTTPDManager.stop(this);
@ -456,8 +423,6 @@ public class SwapService extends Service {
stopPollingConnectedSwapRepo();
//TODO getBluetoothSwap().stopInBackground();
if (timer != null) {
timer.cancel();
}
@ -564,7 +529,8 @@ public class SwapService extends Service {
}
};
private final BroadcastReceiver onBluetoothSwapStateChange = new SwapStateChangeReceiver();
private final BroadcastReceiver bluetoothStatus = new SwapStateChangeReceiver();
private final BroadcastReceiver localRepoStatus = new SwapStateChangeReceiver();
/**
* When swapping is setup, then start the index polling.
@ -574,10 +540,13 @@ public class SwapService extends Service {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.hasExtra(SwapService.EXTRA_STARTED)) {
int bluetoothStatus = intent.getIntExtra(BluetoothManager.ACTION_STATUS, -1);
int wifiStatus = intent.getIntExtra(LocalRepoService.EXTRA_STATUS, -1);
if (bluetoothStatus == BluetoothManager.STATUS_STARTED
|| wifiStatus == LocalRepoService.STATUS_STARTED) {
localBroadcastManager.registerReceiver(pollForUpdatesReceiver,
new IntentFilter());
} else if (intent.hasExtra(SwapService.EXTRA_STOPPING) || intent.hasExtra(SwapService.EXTRA_STOPPED)) {
new IntentFilter(UpdateService.LOCAL_ACTION_STATUS));
} else {
localBroadcastManager.unregisterReceiver(pollForUpdatesReceiver);
}
}
@ -597,4 +566,23 @@ public class SwapService extends Service {
}
}
}
/**
* Handle events if the user or system changes the Bluetooth setup outside of F-Droid.
*/
private final BroadcastReceiver bluetoothScanModeChanged = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, -1)) {
case BluetoothAdapter.SCAN_MODE_NONE:
BluetoothManager.stop(SwapService.this);
break;
case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
BluetoothManager.start(SwapService.this);
break;
}
}
};
}

View File

@ -4,10 +4,11 @@ import android.bluetooth.BluetoothDevice;
import android.os.Parcel;
import android.text.TextUtils;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.localrepo.type.BluetoothSwap;
public class BluetoothPeer implements Peer {
private static final String BLUETOOTH_NAME_TAG = "FDroid:";
private final BluetoothDevice device;
public BluetoothPeer(BluetoothDevice device) {
@ -21,7 +22,7 @@ public class BluetoothPeer implements Peer {
@Override
public String getName() {
return device.getName().replaceAll("^" + BluetoothSwap.BLUETOOTH_NAME_TAG, "");
return device.getName().replaceAll("^" + BLUETOOTH_NAME_TAG, "");
}
@Override

View File

@ -1,209 +0,0 @@
package org.fdroid.fdroid.localrepo.type;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.localrepo.SwapService;
import org.fdroid.fdroid.net.bluetooth.BluetoothServer;
@SuppressWarnings("LineLength")
public class BluetoothSwap {
private static final String TAG = "BluetoothSwap";
public static final String BLUETOOTH_NAME_TAG = "FDroid:";
private static BluetoothSwap mInstance;
@NonNull
private final BluetoothAdapter adapter;
private boolean isDiscoverable;
private boolean isConnected;
@NonNull
protected final Context context;
@Nullable
private BluetoothServer server;
private String deviceBluetoothName;
public static BluetoothSwap create(@NonNull Context context) {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) {
return new NoBluetoothType(context);
}
if (mInstance == null) {
mInstance = new BluetoothSwap(context, adapter);
}
return mInstance;
}
private BluetoothSwap(@NonNull Context context, @NonNull BluetoothAdapter adapter) {
this.context = context;
this.adapter = adapter;
}
public boolean isDiscoverable() {
return isDiscoverable;
}
public boolean isConnected() {
return server != null && server.isRunning() && isConnected;
}
public synchronized void start() {
if (isConnected()) {
Utils.debugLog(TAG, "already running, quitting start()");
return;
}
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, -1)) {
case BluetoothAdapter.SCAN_MODE_NONE:
setConnected(false);
break;
case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
isDiscoverable = true;
if (server != null && server.isRunning()) {
setConnected(true);
}
break;
// Only other is BluetoothAdapter.SCAN_MODE_CONNECTABLE. For now don't handle that.
}
}
};
context.registerReceiver(receiver, new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED));
/*
if (server != null) {
Utils.debugLog(TAG, "Attempting to start Bluetooth swap, but it appears to be running already. Will cancel it so it can be restarted.");
server.close();
server = null;
}*/
if (server == null) {
server = new BluetoothServer(this, context.getFilesDir());
}
sendBroadcast(SwapService.EXTRA_STARTING);
//store the original bluetoothname, and update this one to be unique
deviceBluetoothName = adapter.getName();
/*
Utils.debugLog(TAG, "Prefixing Bluetooth adapter name with " + BLUETOOTH_NAME_TAG + " to make it identifiable as a swap device.");
if (!deviceBluetoothName.startsWith(BLUETOOTH_NAME_TAG)) {
adapter.setName(BLUETOOTH_NAME_TAG + deviceBluetoothName);
}
if (!adapter.getName().startsWith(BLUETOOTH_NAME_TAG)) {
Log.e(TAG, "Couldn't change the name of the Bluetooth adapter, it will not get recognized by other swap clients.");
// TODO: Should we bail here?
}*/
if (!adapter.isEnabled()) {
Utils.debugLog(TAG, "Bluetooth adapter is disabled, attempting to enable.");
if (!adapter.enable()) {
Utils.debugLog(TAG, "Could not enable Bluetooth adapter, so bailing out of Bluetooth swap.");
setConnected(false);
return;
}
}
if (adapter.isEnabled()) {
setConnected(true);
} else {
Log.i(TAG, "Didn't start Bluetooth swapping server, because Bluetooth is disabled and couldn't be enabled.");
setConnected(false);
}
}
public void stop() {
if (server != null && server.isAlive()) {
server.close();
setConnected(false);
} else {
Log.i(TAG, "Attempting to stop Bluetooth swap, but it is not currently running.");
}
}
protected void onStopped() {
Utils.debugLog(TAG, "Resetting bluetooth device name to " + deviceBluetoothName + " after swapping.");
adapter.setName(deviceBluetoothName);
}
protected String getBroadcastAction() {
return SwapService.BLUETOOTH_STATE_CHANGE;
}
protected final void setConnected(boolean connected) {
if (connected) {
isConnected = true;
sendBroadcast(SwapService.EXTRA_STARTED);
} else {
isConnected = false;
sendBroadcast(SwapService.EXTRA_STOPPED);
}
}
/**
* Sends either a {@link org.fdroid.fdroid.localrepo.SwapService#EXTRA_STARTING},
* {@link org.fdroid.fdroid.localrepo.SwapService#EXTRA_STARTED} or
* {@link org.fdroid.fdroid.localrepo.SwapService#EXTRA_STOPPED} broadcast.
*/
protected final void sendBroadcast(String extra) {
if (getBroadcastAction() != null) {
Intent intent = new Intent(getBroadcastAction());
intent.putExtra(extra, true);
Utils.debugLog(TAG, "Sending broadcast " + extra + " from " + getClass().getSimpleName());
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
}
public void startInBackground() {
// TODO switch to thread which is killed if still running, like WiFiStateChangeService
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
start();
return null;
}
}.execute();
}
private static class NoBluetoothType extends BluetoothSwap {
NoBluetoothType(@NonNull Context context) {
super(context, null);
}
@Override
public void start() {
}
@Override
public void stop() {
}
@Override
protected String getBroadcastAction() {
return null;
}
}
}

View File

@ -7,62 +7,26 @@ import android.bluetooth.BluetoothSocket;
import java.io.IOException;
public class BluetoothClient {
@SuppressWarnings("unused")
private static final String TAG = "BluetoothClient";
private final BluetoothDevice device;
public BluetoothClient(BluetoothDevice device) {
this.device = device;
}
public BluetoothClient(String macAddress) {
device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress);
}
public BluetoothConnection openConnection() throws IOException {
BluetoothSocket socket = null;
BluetoothConnection connection = null;
try {
socket = device.createInsecureRfcommSocketToServiceRecord(BluetoothConstants.fdroidUuid());
BluetoothSocket socket = device.createInsecureRfcommSocketToServiceRecord(BluetoothConstants.fdroidUuid());
connection = new BluetoothConnection(socket);
connection.open();
return connection;
} catch (IOException e1) {
} finally {
if (connection != null) {
connection.closeQuietly();
}
throw e1;
/*
Log.e(TAG, "There was an error while establishing Bluetooth connection. Falling back to reflection");
Class<?> clazz = socket.getRemoteDevice().getClass();
Class<?>[] paramTypes = new Class<?>[]{Integer.TYPE};
Method method;
try {
method = clazz.getMethod("createInsecureRfcommSocket", paramTypes);
Object[] params = new Object[]{1};
BluetoothSocket sockFallback = (BluetoothSocket) method.invoke(socket.getRemoteDevice(), params);
BluetoothConnection connection = new BluetoothConnection(sockFallback);
connection.open();
return connection;
} catch (NoSuchMethodException e) {
throw e1;
} catch (IllegalAccessException e) {
throw e1;
} catch (InvocationTargetException e) {
throw e1;
}*/
// Don't catch exceptions this time, let it bubble up as we did our best but don't
// have anythign else to offer in terms of resolving the problem right now.
}
}
}

View File

@ -7,7 +7,6 @@ import android.util.Log;
import android.webkit.MimeTypeMap;
import fi.iki.elonen.NanoHTTPD;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.localrepo.type.BluetoothSwap;
import org.fdroid.fdroid.net.bluetooth.httpish.Request;
import org.fdroid.fdroid.net.bluetooth.httpish.Response;
@ -34,18 +33,9 @@ public class BluetoothServer extends Thread {
private final List<ClientConnection> clients = new ArrayList<>();
private final File webRoot;
private final BluetoothSwap swap;
private boolean isRunning;
public BluetoothServer(BluetoothSwap swap, File webRoot) {
public BluetoothServer(File webRoot) {
this.webRoot = webRoot;
this.swap = swap;
start();
}
public boolean isRunning() {
return isRunning;
}
public void close() {
@ -64,15 +54,12 @@ public class BluetoothServer extends Thread {
@Override
public void run() {
isRunning = true;
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
try {
serverSocket = adapter.listenUsingInsecureRfcommWithServiceRecord("FDroid App Swap", BluetoothConstants.fdroidUuid());
} catch (IOException e) {
Log.e(TAG, "Error starting Bluetooth server socket, will stop the server now", e);
swap.stop();
isRunning = false;
return;
}
@ -102,7 +89,6 @@ public class BluetoothServer extends Thread {
Log.e(TAG, "Error receiving client connection over Bluetooth server socket, will continue listening for other clients", e);
}
}
isRunning = false;
}
private static class ClientConnection extends Thread {

View File

@ -26,6 +26,7 @@ import cc.mvdan.accesspoint.WifiApControl;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.localrepo.BluetoothManager;
import org.fdroid.fdroid.localrepo.SwapService;
import org.fdroid.fdroid.localrepo.SwapView;
import org.fdroid.fdroid.localrepo.peers.Peer;
@ -135,7 +136,6 @@ public class StartSwapView extends SwapView {
bluetoothSwitch.setOnCheckedChangeListener(null);
}
LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(onBluetoothSwapStateChanged);
LocalBroadcastManager.getInstance(getContext()).unregisterReceiver(onWifiNetworkChanged);
}
@ -170,13 +170,6 @@ public class StartSwapView extends SwapView {
getActivity().sendFDroid();
}
});
findViewById(R.id.btn_qr_scanner).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
getActivity().startQrWorkflow();
}
});
}
/**
@ -218,57 +211,17 @@ public class StartSwapView extends SwapView {
viewBluetoothId.setVisibility(bluetooth.isEnabled() ? View.VISIBLE : View.GONE);
textBluetoothVisible = findViewById(R.id.bluetooth_visible);
if (getActivity().getSwapService().isBluetoothDiscoverable()) {
textBluetoothVisible.setText(R.string.swap_visible_bluetooth);
} else {
textBluetoothVisible.setText(R.string.swap_not_visible_bluetooth);
}
bluetoothSwitch = (SwitchCompat) findViewById(R.id.switch_bluetooth);
Utils.debugLog(TAG, getActivity().getSwapService().isBluetoothDiscoverable()
? "Initially marking switch as checked, because Bluetooth is discoverable."
: "Initially marking switch as not-checked, because Bluetooth is not discoverable.");
bluetoothSwitch.setOnCheckedChangeListener(onBluetoothSwitchToggled);
setBluetoothSwitchState(getActivity().getSwapService().isBluetoothDiscoverable(), true);
LocalBroadcastManager.getInstance(getContext()).registerReceiver(onBluetoothSwapStateChanged,
new IntentFilter(SwapService.BLUETOOTH_STATE_CHANGE));
bluetoothSwitch.setChecked(SwapService.getBluetoothVisibleUserPreference());
bluetoothSwitch.setEnabled(true);
bluetoothSwitch.setOnCheckedChangeListener(onBluetoothSwitchToggled);
} else {
findViewById(R.id.bluetooth_info).setVisibility(View.GONE);
}
}
private final 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);
}
}
}
};
private void setBluetoothSwitchState(boolean isChecked, boolean isEnabled) {
bluetoothSwitch.setOnCheckedChangeListener(null);
bluetoothSwitch.setChecked(isChecked);
bluetoothSwitch.setEnabled(isEnabled);
bluetoothSwitch.setOnCheckedChangeListener(onBluetoothSwitchToggled);
}
private final CompoundButton.OnCheckedChangeListener onBluetoothSwitchToggled = new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
@ -281,7 +234,7 @@ public class StartSwapView extends SwapView {
// 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.");
getActivity().getSwapService().getBluetoothSwap().stop();
BluetoothManager.stop(getContext());
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.");

View File

@ -62,11 +62,13 @@ import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.installer.InstallManagerService;
import org.fdroid.fdroid.installer.Installer;
import org.fdroid.fdroid.localrepo.BluetoothManager;
import org.fdroid.fdroid.localrepo.BonjourManager;
import org.fdroid.fdroid.localrepo.LocalHTTPDManager;
import org.fdroid.fdroid.localrepo.LocalRepoService;
import org.fdroid.fdroid.localrepo.SwapService;
import org.fdroid.fdroid.localrepo.SwapView;
import org.fdroid.fdroid.localrepo.peers.BluetoothPeer;
import org.fdroid.fdroid.localrepo.peers.Peer;
import org.fdroid.fdroid.net.BluetoothDownloader;
import org.fdroid.fdroid.net.Downloader;
@ -349,6 +351,10 @@ public class SwapWorkflowActivity extends AppCompatActivity {
localBroadcastManager.registerReceiver(repoUpdateReceiver,
new IntentFilter(UpdateService.LOCAL_ACTION_STATUS));
localBroadcastManager.registerReceiver(bonjourStatus, new IntentFilter(BonjourManager.ACTION_STATUS));
localBroadcastManager.registerReceiver(bluetoothStatus, new IntentFilter(BluetoothManager.ACTION_STATUS));
registerReceiver(bluetoothScanModeChanged,
new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED));
checkIncomingIntent();
@ -362,10 +368,13 @@ public class SwapWorkflowActivity extends AppCompatActivity {
protected void onPause() {
super.onPause();
unregisterReceiver(bluetoothScanModeChanged);
localBroadcastManager.unregisterReceiver(onWifiStateChanged);
localBroadcastManager.unregisterReceiver(localRepoStatus);
localBroadcastManager.unregisterReceiver(repoUpdateReceiver);
localBroadcastManager.unregisterReceiver(bonjourStatus);
localBroadcastManager.unregisterReceiver(bluetoothStatus);
}
@Override
@ -522,24 +531,6 @@ public class SwapWorkflowActivity extends AppCompatActivity {
inflateSwapView(R.layout.swap_start_swap);
}
public void startQrWorkflow() {
if (!getSwapService().isEnabled()) {
new AlertDialog.Builder(this)
.setTitle(R.string.not_visible_nearby)
.setMessage(R.string.not_visible_nearby_description)
.setCancelable(true)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Do nothing. The dialog will get dismissed anyway, which is all we ever wanted...
}
})
.create().show();
} else {
inflateSwapView(R.layout.swap_wifi_qr);
}
}
/**
* On {@code android-26}, only apps with privileges can access
* {@code WRITE_SETTINGS}. So this just shows the tethering settings
@ -720,7 +711,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
if (resultCode != RESULT_CANCELED) {
Utils.debugLog(TAG, "User made Bluetooth discoverable, will proceed to start bluetooth server.");
getSwapService().getBluetoothSwap().startInBackground(); // TODO replace with Intent to SwapService
BluetoothManager.start(this);
} else {
Utils.debugLog(TAG, "User chose not to make Bluetooth discoverable, so doing nothing");
SwapService.putBluetoothVisibleUserPreference(false);
@ -759,21 +750,42 @@ public class SwapWorkflowActivity extends AppCompatActivity {
private void ensureBluetoothDiscoverableThenStart() {
Utils.debugLog(TAG, "Ensuring Bluetooth is in discoverable mode.");
if (bluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
// TODO: Listen for BluetoothAdapter.ACTION_SCAN_MODE_CHANGED and respond if discovery
// is cancelled prematurely.
// 3600 is new maximum! TODO: What about when this expires? What if user manually disables discovery?
final int discoverableTimeout = 3600;
Utils.debugLog(TAG, "Not currently in discoverable mode, so prompting user to enable.");
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, discoverableTimeout);
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 3600); // 1 hour
startActivityForResult(intent, REQUEST_BLUETOOTH_DISCOVERABLE);
}
service.getBluetoothSwap().startInBackground(); // TODO replace with Intent to SwapService
BluetoothManager.start(this);
}
private final BroadcastReceiver bluetoothScanModeChanged = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
SwitchCompat bluetoothSwitch = container.findViewById(R.id.switch_bluetooth);
TextView textBluetoothVisible = container.findViewById(R.id.bluetooth_visible);
if (bluetoothSwitch == null || textBluetoothVisible == null
|| !BluetoothManager.ACTION_STATUS.equals(intent.getAction())) {
return;
}
switch (intent.getIntExtra(BluetoothAdapter.EXTRA_SCAN_MODE, -1)) {
case BluetoothAdapter.SCAN_MODE_NONE:
textBluetoothVisible.setText(R.string.disabled);
bluetoothSwitch.setEnabled(true);
break;
case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
textBluetoothVisible.setText(R.string.swap_visible_bluetooth);
bluetoothSwitch.setEnabled(true);
break;
case BluetoothAdapter.SCAN_MODE_CONNECTABLE:
textBluetoothVisible.setText(R.string.swap_not_visible_bluetooth);
bluetoothSwitch.setEnabled(true);
break;
}
}
};
/**
* Helper class to try and make sense of what the swap workflow is currently doing.
* The more technologies are involved in the process (e.g. Bluetooth/Wifi/NFC/etc)
@ -799,7 +811,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
if (service == null) {
message = "No swap service";
} else {
String bluetooth = getSwapService().getBluetoothSwap().isConnected() ? "Y" : " N";
String bluetooth;
bluetooth = "N/A";
if (bluetoothAdapter != null) {
@ -1015,6 +1027,13 @@ public class SwapWorkflowActivity extends AppCompatActivity {
}
});
findViewById(R.id.btn_scan_qr).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
inflateSwapView(R.layout.swap_wifi_qr);
}
});
if (SwapService.getWifiVisibleUserPreference()) {
wifiSwitch.setChecked(true);
} else {
@ -1085,6 +1104,82 @@ public class SwapWorkflowActivity extends AppCompatActivity {
}
};
private final BroadcastReceiver bluetoothStatus = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
SwitchCompat bluetoothSwitch = container.findViewById(R.id.switch_bluetooth);
TextView textBluetoothVisible = container.findViewById(R.id.bluetooth_visible);
TextView textDeviceIdBluetooth = container.findViewById(R.id.device_id_bluetooth);
TextView peopleNearbyText = container.findViewById(R.id.text_people_nearby);
ProgressBar peopleNearbyProgress = container.findViewById(R.id.searching_people_nearby);
if (bluetoothSwitch == null || textBluetoothVisible == null || textDeviceIdBluetooth == null
|| peopleNearbyText == null || peopleNearbyProgress == null
|| !BluetoothManager.ACTION_STATUS.equals(intent.getAction())) {
return;
}
int status = intent.getIntExtra(BluetoothManager.EXTRA_STATUS, -1);
Log.i(TAG, "BluetoothManager.EXTRA_STATUS: " + status);
switch (status) {
case BluetoothManager.STATUS_STARTING:
bluetoothSwitch.setEnabled(false);
textBluetoothVisible.setText(R.string.swap_setting_up_bluetooth);
textDeviceIdBluetooth.setVisibility(View.VISIBLE);
peopleNearbyText.setText(R.string.swap_scanning_for_peers);
peopleNearbyText.setVisibility(View.VISIBLE);
peopleNearbyProgress.setVisibility(View.VISIBLE);
break;
case BluetoothManager.STATUS_STARTED:
bluetoothSwitch.setEnabled(true);
textBluetoothVisible.setText(R.string.swap_visible_bluetooth);
textDeviceIdBluetooth.setVisibility(View.VISIBLE);
peopleNearbyText.setText(R.string.swap_scanning_for_peers);
peopleNearbyText.setVisibility(View.VISIBLE);
peopleNearbyProgress.setVisibility(View.VISIBLE);
break;
case BluetoothManager.STATUS_STOPPING:
bluetoothSwitch.setEnabled(false);
textBluetoothVisible.setText(R.string.swap_stopping);
textDeviceIdBluetooth.setVisibility(View.GONE);
if (!BonjourManager.isAlive()) {
peopleNearbyText.setText(R.string.swap_stopping);
peopleNearbyText.setVisibility(View.VISIBLE);
peopleNearbyProgress.setVisibility(View.VISIBLE);
}
break;
case BluetoothManager.STATUS_STOPPED:
bluetoothSwitch.setEnabled(true);
textBluetoothVisible.setText(R.string.swap_not_visible_bluetooth);
textDeviceIdBluetooth.setVisibility(View.GONE);
if (!BonjourManager.isAlive()) {
peopleNearbyText.setVisibility(View.GONE);
peopleNearbyProgress.setVisibility(View.GONE);
}
ListView peopleNearbyView = container.findViewById(R.id.list_people_nearby);
if (peopleNearbyView == null) {
break;
}
ArrayAdapter peopleNearbyAdapter = (ArrayAdapter) peopleNearbyView.getAdapter();
for (int i = 0; i < peopleNearbyAdapter.getCount(); i++) {
Peer peer = (Peer) peopleNearbyAdapter.getItem(i);
if (peer.getClass().equals(BluetoothPeer.class)) {
Utils.debugLog(TAG, "Removing bluetooth peer: " + peer.getName());
peopleNearbyAdapter.remove(peer);
}
}
break;
case BluetoothManager.STATUS_ERROR:
bluetoothSwitch.setEnabled(true);
textBluetoothVisible.setText(intent.getStringExtra(Intent.EXTRA_TEXT));
textDeviceIdBluetooth.setVisibility(View.VISIBLE);
break;
default:
throw new IllegalArgumentException("Bad intent: " + intent);
}
}
};
private void setUpUseBluetoothButton() {
Button useBluetooth = findViewById(R.id.btn_use_bluetooth);
if (useBluetooth != null) {

View File

@ -42,7 +42,7 @@
android:id="@+id/bluetooth_visible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="@string/swap_visible_bluetooth"
tools:text="@string/swap_not_visible_bluetooth"
android:textSize="18sp"/>
<TextView
@ -169,7 +169,7 @@
android:background="@android:color/transparent"/>
<Button
android:id="@+id/btn_qr_scanner"
android:id="@+id/btn_scan_qr"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableLeft="@drawable/ic_qr_grey"

View File

@ -47,7 +47,7 @@ public class BluetoothConnection {
Utils.closeQuietly(socket);
}
public void close() throws IOException {
public void close() {
closeQuietly();
}
}

View File

@ -25,7 +25,6 @@ import android.annotation.TargetApi;
import android.app.Activity;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
@ -702,7 +701,7 @@ public class AppDetailsActivity extends AppCompatActivity
if (Build.VERSION.SDK_INT < 18) {
return BluetoothAdapter.getDefaultAdapter();
}
return ((BluetoothManager) getSystemService(BLUETOOTH_SERVICE)).getAdapter();
return ((android.bluetooth.BluetoothManager) getSystemService(BLUETOOTH_SERVICE)).getAdapter();
}
@Override

View File

@ -490,8 +490,10 @@ This often occurs with apps installed via Google Play or other sources, if they
<string name="swap_intro">Connect and trade apps with people near you.</string>
<string name="swap_starting">Starting…</string>
<string name="swap_stopping">Stopping…</string>
<string name="disabled">Disabled</string>
<string name="swap_visible_bluetooth">Visible via Bluetooth</string>
<string name="swap_setting_up_bluetooth">Setting up Bluetooth…</string>
<string name="swap_error_cannot_start_bluetooth">Cannot start Bluetooth!</string>
<string name="swap_not_visible_bluetooth">Not visible via Bluetooth</string>
<string name="swap_visible_wifi">Visible via Wi-Fi</string>
<string name="swap_setting_up_wifi">Setting up Wi-Fi…</string>