diff --git a/F-Droid/AndroidManifest.xml b/F-Droid/AndroidManifest.xml
index 2cbd97796..42b92faf2 100644
--- a/F-Droid/AndroidManifest.xml
+++ b/F-Droid/AndroidManifest.xml
@@ -44,6 +44,7 @@
+
diff --git a/F-Droid/res/layout/swap_join_wifi.xml b/F-Droid/res/layout/swap_join_wifi.xml
index b59f544d1..bd778bfb5 100644
--- a/F-Droid/res/layout/swap_join_wifi.xml
+++ b/F-Droid/res/layout/swap_join_wifi.xml
@@ -25,12 +25,12 @@
android:layout_below="@+id/text_description"
android:layout_centerHorizontal="true" />
-
+ - #222
+
+
diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiFragment.java b/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiFragment.java
index 9723945b9..800c05eef 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiFragment.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiFragment.java
@@ -18,6 +18,7 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
+import android.widget.Button;
import android.widget.TextView;
import org.fdroid.fdroid.FDroidApp;
@@ -56,6 +57,15 @@ public class JoinWifiFragment extends Fragment {
openAvailableNetworks();
}
});
+
+ Button bluetooth = (Button)joinWifiView.findViewById(R.id.btn_bluetooth);
+ bluetooth.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ ((SwapProcessManager)getActivity()).connectWithBluetooth();
+ }
+ });
+
return joinWifiView;
}
diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapActivity.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapActivity.java
index 2c556bf14..654b705dc 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapActivity.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapActivity.java
@@ -1,7 +1,9 @@
package org.fdroid.fdroid.views.swap;
import android.app.ProgressDialog;
+import android.bluetooth.BluetoothAdapter;
import android.content.Context;
+import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -10,6 +12,7 @@ import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.ActionBarActivity;
+import android.util.Log;
import android.view.MenuItem;
import android.widget.Toast;
@@ -19,6 +22,7 @@ import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.localrepo.LocalRepoManager;
+import org.fdroid.fdroid.net.bluetooth.BluetoothServer;
import java.util.Set;
import java.util.Timer;
@@ -31,6 +35,11 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
private static final String STATE_JOIN_WIFI = "joinWifi";
private static final String STATE_NFC = "nfc";
private static final String STATE_WIFI_QR = "wifiQr";
+ private static final String STATE_BLUETOOTH_DEVICE_LIST = "bluetoothDeviceList";
+
+ private static final int REQUEST_ENABLE_BLUETOOTH = 1;
+
+ private static final String TAG = "org.fdroid.fdroid.views.swap.SwapActivity";
private Timer shutdownLocalRepoTimer;
private UpdateAsyncTask updateSwappableAppsTask = null;
@@ -141,14 +150,14 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
return false;
}
- private void showBluetooth() {
-
- }
-
private void showWifiQr() {
showFragment(new WifiQrFragment(), STATE_WIFI_QR);
}
+ private void showBluetoothDeviceList() {
+ showFragment(new BluetoothDeviceListFragment(), STATE_BLUETOOTH_DEVICE_LIST);
+ }
+
private void showFragment(Fragment fragment, String name) {
getSupportFragmentManager()
.beginTransaction()
@@ -215,6 +224,55 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
finish();
}
+ /**
+ * The process for setting up bluetooth is as follows:
+ * * Assume we have bluetooth available (otherwise the button which allowed us to start
+ * the bluetooth process should not have been available). TODO: Remove button if bluetooth unavailable.
+ * * Ask user to enable (if not enabled yet).
+ * * Start bluetooth server socket.
+ * * Enable bluetooth discoverability, so that people can connect to our server socket.
+ *
+ * Note that this is a little different than the usual process for bluetooth _clients_, which
+ * involves pairing and connecting with other devices.
+ */
+ @Override
+ public void connectWithBluetooth() {
+
+ Log.d(TAG, "Initiating Bluetooth swap instead of wifi.");
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter.isEnabled()) {
+ Log.d(TAG, "Bluetooth enabled, will pair with device.");
+ startBluetoothServer();
+ } else {
+ Log.d(TAG, "Bluetooth disabled, asking user to enable it.");
+ Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
+ startActivityForResult(enableBtIntent, REQUEST_ENABLE_BLUETOOTH);
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ if (requestCode == REQUEST_ENABLE_BLUETOOTH) {
+
+ if (resultCode == RESULT_OK) {
+ Log.d(TAG, "User enabled Bluetooth, will pair with device.");
+ startBluetoothServer();
+ } else {
+ // Didn't enable bluetooth
+ Log.d(TAG, "User chose not to enable Bluetooth, so doing nothing (i.e. sticking with wifi).");
+ }
+
+ }
+ }
+
+ private void startBluetoothServer() {
+ Log.d(TAG, "Starting bluetooth server.");
+ new BluetoothServer().start();
+ showBluetoothDeviceList();
+ }
+
class UpdateAsyncTask extends AsyncTask {
@SuppressWarnings("UnusedDeclaration")
diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapProcessManager.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapProcessManager.java
index cb9567bf9..9d2fbc9b4 100644
--- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapProcessManager.java
+++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapProcessManager.java
@@ -9,4 +9,5 @@ package org.fdroid.fdroid.views.swap;
public interface SwapProcessManager {
void nextStep();
void stopSwapping();
+ void connectWithBluetooth();
}
diff --git a/bluetooth-notes.txt b/bluetooth-notes.txt
new file mode 100644
index 000000000..918d0ca13
--- /dev/null
+++ b/bluetooth-notes.txt
@@ -0,0 +1,21 @@
+One is server, the other is the client (always the case with Bluetooth).
+
+When does the pairing happen? I can think of a few times:
+
+Use case 1 -
+ * Swapper decides to use bluetooth to send apps to others.
+ * Selects "Use bluetooth instead" on the "join wifi" screen.
+ * Starts a bluetooth server
+ + Make itself discoverable
+ + Opens a bluetooth server socket
+ + Waits for incoming client connections.
+
+ * Swapee opens swap workflow
+ * Selects the bluetooth option
+ * Is asked to pair with nearby bluetooth devices, using the F-Droid UUID to make sure it doesn't connect to, e.g. bluetooth headphones.
+ * Stays connected in the background
+ * Adds the repo as per usual (with a url such as bluetooth://device-mac-address)
+ * When repo updates, it uses the open connection to get data
+ * If the connection has closed, attempts to reconnect
+ * Same when downloading files
+
diff --git a/res/layout/swap_bluetooth_header.xml b/res/layout/swap_bluetooth_header.xml
new file mode 100644
index 000000000..ae9788b29
--- /dev/null
+++ b/res/layout/swap_bluetooth_header.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/org/fdroid/fdroid/net/bluetooth/BluetoothDownloader.java b/src/org/fdroid/fdroid/net/BluetoothDownloader.java
similarity index 61%
rename from src/org/fdroid/fdroid/net/bluetooth/BluetoothDownloader.java
rename to src/org/fdroid/fdroid/net/BluetoothDownloader.java
index da66f67d6..c1c770056 100644
--- a/src/org/fdroid/fdroid/net/bluetooth/BluetoothDownloader.java
+++ b/src/org/fdroid/fdroid/net/BluetoothDownloader.java
@@ -1,49 +1,45 @@
-package org.fdroid.fdroid.net.bluetooth;
+package org.fdroid.fdroid.net;
import android.content.Context;
import android.util.Log;
-import org.fdroid.fdroid.net.Downloader;
+import org.fdroid.fdroid.net.bluetooth.BluetoothClient;
+import org.fdroid.fdroid.net.bluetooth.FileDetails;
import org.fdroid.fdroid.net.bluetooth.httpish.Request;
import org.fdroid.fdroid.net.bluetooth.httpish.Response;
-import java.io.*;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.net.MalformedURLException;
public class BluetoothDownloader extends Downloader {
- private static final String TAG = "org.fdroid.fdroid.net.bluetooth.BluetoothDownloader";
+ private static final String TAG = "org.fdroid.fdroid.net.BluetoothDownloader";
private BluetoothClient client;
private FileDetails fileDetails;
- public BluetoothDownloader(BluetoothClient client, String destFile, Context ctx) throws FileNotFoundException, MalformedURLException {
+ BluetoothDownloader(BluetoothClient client, String destFile, Context ctx) throws FileNotFoundException, MalformedURLException {
super(destFile, ctx);
- this.client = client;
}
- public BluetoothDownloader(BluetoothClient client, Context ctx) throws IOException {
+ BluetoothDownloader(BluetoothClient client, Context ctx) throws IOException {
super(ctx);
- this.client = client;
}
- public BluetoothDownloader(BluetoothClient client, File destFile) throws FileNotFoundException, MalformedURLException {
+ BluetoothDownloader(BluetoothClient client, File destFile) throws FileNotFoundException, MalformedURLException {
super(destFile);
- this.client = client;
}
- public BluetoothDownloader(BluetoothClient client, File destFile, Context ctx) throws IOException {
- super(destFile, ctx);
- this.client = client;
- }
-
- public BluetoothDownloader(BluetoothClient client, OutputStream output) throws MalformedURLException {
+ BluetoothDownloader(BluetoothClient client, OutputStream output) throws MalformedURLException {
super(output);
- this.client = client;
}
@Override
- public InputStream inputStream() throws IOException {
- Response response = new Request(Request.Methods.GET, client).send();
+ public InputStream getInputStream() throws IOException {
+ Response response = Request.createGET(sourceUrl.getPath(), client.openConnection()).send();
fileDetails = response.toFileDetails();
return response.toContentStream();
}
@@ -58,7 +54,7 @@ public class BluetoothDownloader extends Downloader {
if (fileDetails == null) {
Log.d(TAG, "Going to Bluetooth \"server\" to get file details.");
try {
- fileDetails = new Request(Request.Methods.HEAD, client).send().toFileDetails();
+ fileDetails = Request.createHEAD(sourceUrl.getPath(), client.openConnection()).send().toFileDetails();
} catch (IOException e) {
Log.e(TAG, "Error getting file details from Bluetooth \"server\": " + e.getMessage());
}
diff --git a/src/org/fdroid/fdroid/net/bluetooth/BluetoothClient.java b/src/org/fdroid/fdroid/net/bluetooth/BluetoothClient.java
index 68ddf52e6..4f77b80a1 100644
--- a/src/org/fdroid/fdroid/net/bluetooth/BluetoothClient.java
+++ b/src/org/fdroid/fdroid/net/bluetooth/BluetoothClient.java
@@ -2,22 +2,18 @@ package org.fdroid.fdroid.net.bluetooth;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothSocket;
-import android.util.Log;
-import org.fdroid.fdroid.Utils;
-import java.io.*;
-import java.util.UUID;
+import java.io.IOException;
public class BluetoothClient {
private static final String TAG = "org.fdroid.fdroid.net.bluetooth.BluetoothClient";
- private BluetoothAdapter adapter;
+ private final BluetoothAdapter adapter;
private BluetoothDevice device;
- public BluetoothClient(BluetoothAdapter adapter) {
- this.adapter = adapter;
+ public BluetoothClient() {
+ this.adapter = BluetoothAdapter.getDefaultAdapter();
}
public void pairWithDevice() throws IOException {
@@ -27,55 +23,15 @@ public class BluetoothClient {
}
// TODO: Don't just take a random bluetooth device :)
+
device = adapter.getBondedDevices().iterator().next();
+ device.createRfcommSocketToServiceRecord(BluetoothConstants.fdroidUuid());
}
- public Connection openConnection() throws IOException {
- return new Connection();
+ public BluetoothConnection openConnection() throws IOException {
+ return null;
+ // return new BluetoothConnection();
}
- public class Connection {
-
- private InputStream input = null;
- private OutputStream output = null;
-
- private BluetoothSocket socket;
-
- private Connection() throws IOException {
- Log.d(TAG, "Attempting to create connection to Bluetooth device '" + device.getName() + "'...");
- socket = device.createRfcommSocketToServiceRecord(UUID.fromString(BluetoothConstants.fdroidUuid()));
- }
-
- public InputStream getInputStream() {
- return input;
- }
-
- public OutputStream getOutputStream() {
- return output;
- }
-
- public void open() throws IOException {
- socket.connect();
- input = socket.getInputStream();
- output = socket.getOutputStream();
- Log.d(TAG, "Opened connection to Bluetooth device '" + device.getName() + "'");
- }
-
- public void closeQuietly() {
- Utils.closeQuietly(input);
- Utils.closeQuietly(output);
- Utils.closeQuietly(socket);
- }
-
- public void close() throws IOException {
- if (input == null || output == null) {
- throw new RuntimeException("Cannot close() a BluetoothConnection before calling open()" );
- }
-
- input.close();
- output.close();
- socket.close();
- }
- }
}
diff --git a/src/org/fdroid/fdroid/net/bluetooth/BluetoothConnection.java b/src/org/fdroid/fdroid/net/bluetooth/BluetoothConnection.java
new file mode 100644
index 000000000..43ba63d8d
--- /dev/null
+++ b/src/org/fdroid/fdroid/net/bluetooth/BluetoothConnection.java
@@ -0,0 +1,61 @@
+package org.fdroid.fdroid.net.bluetooth;
+
+import android.annotation.TargetApi;
+import android.bluetooth.BluetoothSocket;
+import android.os.Build;
+import android.util.Log;
+import org.fdroid.fdroid.Utils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class BluetoothConnection {
+
+ private static final String TAG = "org.fdroid.fdroid.net.bluetooth.BluetoothConnection";
+
+ private InputStream input = null;
+ private OutputStream output = null;
+ protected final BluetoothSocket socket;
+
+ public BluetoothConnection(BluetoothSocket socket) throws IOException {
+ this.socket = socket;
+ }
+
+ public InputStream getInputStream() {
+ return input;
+ }
+
+ public OutputStream getOutputStream() {
+ return output;
+ }
+
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ public void open() throws IOException {
+ if (!socket.isConnected()) {
+ // Server sockets will already be connected when they are passed to us,
+ // client sockets require us to call connect().
+ socket.connect();
+ }
+
+ input = socket.getInputStream();
+ output = socket.getOutputStream();
+ Log.d(TAG, "Opened connection to Bluetooth device");
+ }
+
+ public void closeQuietly() {
+ Utils.closeQuietly(input);
+ Utils.closeQuietly(output);
+ Utils.closeQuietly(socket);
+ }
+
+ public void close() throws IOException {
+ if (input == null || output == null) {
+ throw new RuntimeException("Cannot close() a BluetoothConnection before calling open()" );
+ }
+
+ input.close();
+ output.close();
+ socket.close();
+ }
+}
\ No newline at end of file
diff --git a/src/org/fdroid/fdroid/net/bluetooth/BluetoothConstants.java b/src/org/fdroid/fdroid/net/bluetooth/BluetoothConstants.java
index 7a44a480c..e0876462d 100644
--- a/src/org/fdroid/fdroid/net/bluetooth/BluetoothConstants.java
+++ b/src/org/fdroid/fdroid/net/bluetooth/BluetoothConstants.java
@@ -1,14 +1,16 @@
package org.fdroid.fdroid.net.bluetooth;
+import java.util.UUID;
+
/**
* We need some shared information between the client and the server app.
*/
public class BluetoothConstants {
- public static String fdroidUuid() {
+ public static UUID fdroidUuid() {
// TODO: Generate a UUID deterministically from, e.g. "org.fdroid.fdroid.net.Bluetooth";
// This UUID is just from the first example at http://www.ietf.org/rfc/rfc4122.txt
- return "f81d4fae-7dec-11d0-a765-00a0c91e6bf6";
+ return UUID.fromString("f81d4fae-7dec-11d0-a765-00a0c91e6bf6");
}
}
diff --git a/src/org/fdroid/fdroid/net/bluetooth/BluetoothServer.java b/src/org/fdroid/fdroid/net/bluetooth/BluetoothServer.java
new file mode 100644
index 000000000..570f5eba0
--- /dev/null
+++ b/src/org/fdroid/fdroid/net/bluetooth/BluetoothServer.java
@@ -0,0 +1,112 @@
+package org.fdroid.fdroid.net.bluetooth;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothServerSocket;
+import android.bluetooth.BluetoothSocket;
+import android.util.Log;
+import org.fdroid.fdroid.Utils;
+import org.fdroid.fdroid.net.bluetooth.httpish.Request;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Act as a layer on top of LocalHTTPD server, by forwarding requests served
+ * over bluetooth to that server.
+ */
+public class BluetoothServer extends Thread {
+
+ private static final String TAG = "org.fdroid.fdroid.net.bluetooth.BluetoothServer";
+
+ private BluetoothServerSocket serverSocket;
+
+ private List clients = new ArrayList();
+
+ public void close() {
+
+ for (Connection connection : clients) {
+ connection.interrupt();
+ }
+
+ if (serverSocket != null) {
+ Utils.closeQuietly(serverSocket);
+ }
+
+ }
+
+ @Override
+ public void run() {
+
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ try {
+ serverSocket = adapter.listenUsingRfcommWithServiceRecord("FDroid App Swap", BluetoothConstants.fdroidUuid());
+ } catch (IOException e) {
+ Log.e(TAG, "Error starting Bluetooth server socket, will stop the server now - " + e.getMessage());
+ return;
+ }
+
+ while (true) {
+ try {
+ BluetoothSocket clientSocket = serverSocket.accept();
+ if (clientSocket != null && !isInterrupted()) {
+ Connection client = new Connection(clientSocket);
+ client.start();
+ clients.add(client);
+ } else {
+ break;
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Error receiving client connection over Bluetooth server socket, will continue listening for other clients - " + e.getMessage());
+ }
+ }
+
+ }
+
+ private static class Connection extends Thread
+ {
+
+ private static final String TAG = "org.fdroid.fdroid.net.bluetooth.BluetoothServer.Connection";
+ private BluetoothSocket socket;
+
+ public Connection(BluetoothSocket socket) {
+ this.socket = socket;
+ }
+
+ @Override
+ public void run() {
+
+ Log.d(TAG, "Listening for incoming Bluetooth requests from client");
+
+ BluetoothConnection connection;
+ try {
+ connection = new BluetoothConnection(socket);
+ } catch (IOException e) {
+ Log.e(TAG, "Error listening for incoming connections over bluetooth - " + e.getMessage());
+ return;
+ }
+
+ while (true) {
+
+ try {
+ Log.d(TAG, "Listening for new Bluetooth request from client.");
+ Request incomingRequest = Request.listenForRequest(connection);
+ handleRequest(incomingRequest);
+ } catch (IOException e) {
+ Log.e(TAG, "Error receiving incoming connection over bluetooth - " + e.getMessage());
+ }
+
+ if (isInterrupted())
+ break;
+
+ }
+
+ }
+
+ private void handleRequest(Request request) {
+
+ Log.d(TAG, "Received Bluetooth request from client, will process it now.");
+
+ }
+ }
+}
diff --git a/src/org/fdroid/fdroid/net/bluetooth/httpish/Request.java b/src/org/fdroid/fdroid/net/bluetooth/httpish/Request.java
index 32ae07eef..3066c7142 100644
--- a/src/org/fdroid/fdroid/net/bluetooth/httpish/Request.java
+++ b/src/org/fdroid/fdroid/net/bluetooth/httpish/Request.java
@@ -1,37 +1,53 @@
package org.fdroid.fdroid.net.bluetooth.httpish;
-import org.fdroid.fdroid.net.bluetooth.BluetoothClient;
+import org.fdroid.fdroid.net.bluetooth.BluetoothConnection;
import java.io.*;
import java.util.HashMap;
+import java.util.Locale;
import java.util.Map;
public class Request {
+
public static interface Methods {
public static final String HEAD = "HEAD";
public static final String GET = "GET";
}
- private final BluetoothClient client;
- private final String method;
+ private String method;
+ private String path;
+ private Map headers;
- private BluetoothClient.Connection connection;
+ private BluetoothConnection connection;
private BufferedWriter output;
private BufferedReader input;
- public Request(String method, BluetoothClient client) {
+ private Request(String method, String path, BluetoothConnection connection) {
this.method = method;
- this.client = client;
+ this.path = path;
+ this.connection = connection;
+ }
+
+ public static Request createHEAD(String path, BluetoothConnection connection)
+ {
+ return new Request(Methods.HEAD, path, connection);
+ }
+
+ public static Request createGET(String path, BluetoothConnection connection) {
+ return new Request(Methods.GET, path, connection);
}
public Response send() throws IOException {
- connection = client.openConnection();
output = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));
input = new BufferedReader(new InputStreamReader(connection.getInputStream()));
output.write(method);
+ output.write(' ');
+ output.write(path);
+
+ output.write("\n\n");
int responseCode = readResponseCode();
Map headers = readHeaders();
@@ -44,6 +60,40 @@ public class Request {
}
+ /**
+ * Helper function used by listenForRequest().
+ * The reason it is here is because the listenForRequest() is a static function, which would
+ * need to instantiate it's own InputReaders from the bluetooth connection. However, we already
+ * have that happening in a Request, so it is in some ways simpler to delegate to a member
+ * method like this.
+ */
+ private boolean listen() throws IOException {
+
+ String requestLine = input.readLine();
+
+ if (requestLine == null || requestLine.trim().length() == 0)
+ return false;
+
+ String[] parts = requestLine.split("\\s+");
+
+ // First part is the method (GET/HEAD), second is the path (/fdroid/repo/index.jar)
+ if (parts.length < 2)
+ return false;
+
+ method = parts[0].toUpperCase(Locale.ENGLISH);
+ path = parts[1];
+ headers = readHeaders();
+ return true;
+ }
+
+ /**
+ * This is a blocking method, which will wait until a full Request is received.
+ */
+ public static Request listenForRequest(BluetoothConnection connection) throws IOException {
+ Request request = new Request("", "", connection);
+ return request.listen() ? request : null;
+ }
+
/**
* First line of a HTTP response is the status line:
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1
diff --git a/src/org/fdroid/fdroid/views/swap/BluetoothDeviceListFragment.java b/src/org/fdroid/fdroid/views/swap/BluetoothDeviceListFragment.java
new file mode 100644
index 000000000..f9e68699b
--- /dev/null
+++ b/src/org/fdroid/fdroid/views/swap/BluetoothDeviceListFragment.java
@@ -0,0 +1,111 @@
+package org.fdroid.fdroid.views.swap;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.view.ContextThemeWrapper;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+import org.fdroid.fdroid.FDroidApp;
+import org.fdroid.fdroid.R;
+import org.fdroid.fdroid.data.InstalledAppProvider;
+import org.fdroid.fdroid.views.fragments.ThemeableListFragment;
+
+import java.util.List;
+
+public class BluetoothDeviceListFragment extends ThemeableListFragment {
+
+ private Adapter adapter = null;
+
+ private class Adapter extends ArrayAdapter {
+
+ public Adapter(Context context, int resource) {
+ super(context, resource);
+ }
+
+ public Adapter(Context context, int resource, int textViewResourceId) {
+ super(context, resource, textViewResourceId);
+ }
+
+ public Adapter(Context context, int resource, BluetoothDevice[] objects) {
+ super(context, resource, objects);
+ }
+
+ public Adapter(Context context, int resource, int textViewResourceId, BluetoothDevice[] objects) {
+ super(context, resource, textViewResourceId, objects);
+ }
+
+ public Adapter(Context context, int resource, List objects) {
+ super(context, resource, objects);
+ }
+
+ public Adapter(Context context, int resource, int textViewResourceId, List objects) {
+ super(context, resource, textViewResourceId, objects);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View view;
+ if (convertView == null) {
+ view = getActivity().getLayoutInflater().inflate(android.R.layout.simple_list_item_2, parent);
+ } else {
+ view = convertView;
+ }
+
+ BluetoothDevice device = getItem(position);
+ TextView nameView = (TextView)view.findViewById(android.R.id.text1);
+ TextView descriptionView = (TextView)view.findViewById(android.R.id.text2);
+
+ nameView.setText(device.getName());
+ descriptionView.setText(device.getAddress());
+
+ return view;
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(false);
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ setEmptyText("No bluetooth devices found. Is the other device \"discoverable\"?");
+
+ Adapter adapter = new Adapter(
+ new ContextThemeWrapper(getActivity(), R.style.SwapTheme_BluetoothDeviceList_ListItem),
+ R.layout.select_local_apps_list_item
+ );
+
+ setListAdapter(adapter);
+ setListShown(false); // start out with a progress indicator
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ Cursor c = (Cursor) l.getAdapter().getItem(position);
+ String packageName = c.getString(c.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID));
+ if (FDroidApp.selectedApps.contains(packageName)) {
+ FDroidApp.selectedApps.remove(packageName);
+ } else {
+ FDroidApp.selectedApps.add(packageName);
+ }
+ }
+
+ @Override
+ protected int getThemeStyle() {
+ return R.style.SwapTheme_StartSwap;
+ }
+
+ @Override
+ protected int getHeaderLayout() {
+ return R.layout.swap_bluetooth_header;
+ }
+}