WIP: Bluetooth list of paired devices.

This commit is contained in:
Peter Serwylo 2014-10-08 07:41:04 +10:30 committed by Peter Serwylo
parent 45a3efa2b3
commit fba02e32b5
15 changed files with 506 additions and 87 deletions

View File

@ -44,6 +44,7 @@
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" /> android:maxSdkVersion="18" />

View File

@ -25,12 +25,12 @@
android:layout_below="@+id/text_description" android:layout_below="@+id/text_description"
android:layout_centerHorizontal="true" /> android:layout_centerHorizontal="true" />
<!--
<Button style="@style/SwapTheme.Wizard.OptionButton" <Button style="@style/SwapTheme.Wizard.OptionButton"
android:id="@+id/btn_bluetooth" android:id="@+id/btn_bluetooth"
android:text="@string/swap_use_bluetooth" android:text="@string/swap_use_bluetooth"
android:layout_alignParentBottom="true" /> android:layout_alignParentBottom="true" />
<!--
<Button style="@style/SwapTheme.Wizard.OptionButton" <Button style="@style/SwapTheme.Wizard.OptionButton"
android:text="@string/swap_wifi_help" android:text="@string/swap_wifi_help"
android:layout_above="@id/btn_bluetooth" android:layout_above="@id/btn_bluetooth"

View File

@ -43,6 +43,17 @@
<item name="android:background">@color/white</item> <item name="android:background">@color/white</item>
</style> </style>
<style name="SwapTheme.BluetoothDeviceList" parent="AppThemeLightWithDarkActionBar">
</style>
<style name="SwapTheme.BluetoothDeviceList.ListItem" parent="AppThemeLightWithDarkActionBar">
</style>
<style name="SwapTheme.BluetoothDeviceList.Heading" parent="@style/SwapTheme.Wizard.MainText">
<item name="android:textSize">32.5dp</item> <!-- 58px * 96dpi / 160dpi = 32.5sp -->
<item name="android:textColor">#222</item>
</style>
<style name="SwapTheme.AppList" parent="AppThemeLightWithDarkActionBar"> <style name="SwapTheme.AppList" parent="AppThemeLightWithDarkActionBar">
</style> </style>

View File

@ -18,6 +18,7 @@ import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.FDroidApp;
@ -56,6 +57,15 @@ public class JoinWifiFragment extends Fragment {
openAvailableNetworks(); 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; return joinWifiView;
} }

View File

@ -1,7 +1,9 @@
package org.fdroid.fdroid.views.swap; package org.fdroid.fdroid.views.swap;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.bluetooth.BluetoothAdapter;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
@ -10,6 +12,7 @@ import android.support.annotation.NonNull;
import android.support.v4.app.Fragment; import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentManager;
import android.support.v7.app.ActionBarActivity; import android.support.v7.app.ActionBarActivity;
import android.util.Log;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.Toast; import android.widget.Toast;
@ -19,6 +22,7 @@ import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.localrepo.LocalRepoManager; import org.fdroid.fdroid.localrepo.LocalRepoManager;
import org.fdroid.fdroid.net.bluetooth.BluetoothServer;
import java.util.Set; import java.util.Set;
import java.util.Timer; 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_JOIN_WIFI = "joinWifi";
private static final String STATE_NFC = "nfc"; private static final String STATE_NFC = "nfc";
private static final String STATE_WIFI_QR = "wifiQr"; 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 Timer shutdownLocalRepoTimer;
private UpdateAsyncTask updateSwappableAppsTask = null; private UpdateAsyncTask updateSwappableAppsTask = null;
@ -141,14 +150,14 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
return false; return false;
} }
private void showBluetooth() {
}
private void showWifiQr() { private void showWifiQr() {
showFragment(new WifiQrFragment(), STATE_WIFI_QR); showFragment(new WifiQrFragment(), STATE_WIFI_QR);
} }
private void showBluetoothDeviceList() {
showFragment(new BluetoothDeviceListFragment(), STATE_BLUETOOTH_DEVICE_LIST);
}
private void showFragment(Fragment fragment, String name) { private void showFragment(Fragment fragment, String name) {
getSupportFragmentManager() getSupportFragmentManager()
.beginTransaction() .beginTransaction()
@ -215,6 +224,55 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
finish(); 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<Void, String, Void> { class UpdateAsyncTask extends AsyncTask<Void, String, Void> {
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")

View File

@ -9,4 +9,5 @@ package org.fdroid.fdroid.views.swap;
public interface SwapProcessManager { public interface SwapProcessManager {
void nextStep(); void nextStep();
void stopSwapping(); void stopSwapping();
void connectWithBluetooth();
} }

21
bluetooth-notes.txt Normal file
View File

@ -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

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="394dp"
android:layout_height="wrap_content"
android:id="@+id/device_ip_address"
tools:text="Your device name:\nPete's Nexus 4"
style="@style/SwapTheme.BluetoothDeviceList.Heading"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Select from devices below"
style="@style/SwapTheme.Wizard.Text"/>
<ContentLoadingProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/loading_indicator"/>
</LinearLayout>

View File

@ -1,49 +1,45 @@
package org.fdroid.fdroid.net.bluetooth; package org.fdroid.fdroid.net;
import android.content.Context; import android.content.Context;
import android.util.Log; 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.Request;
import org.fdroid.fdroid.net.bluetooth.httpish.Response; 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; import java.net.MalformedURLException;
public class BluetoothDownloader extends Downloader { 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 BluetoothClient client;
private FileDetails fileDetails; 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); super(destFile, ctx);
this.client = client;
} }
public BluetoothDownloader(BluetoothClient client, Context ctx) throws IOException { BluetoothDownloader(BluetoothClient client, Context ctx) throws IOException {
super(ctx); super(ctx);
this.client = client;
} }
public BluetoothDownloader(BluetoothClient client, File destFile) throws FileNotFoundException, MalformedURLException { BluetoothDownloader(BluetoothClient client, File destFile) throws FileNotFoundException, MalformedURLException {
super(destFile); super(destFile);
this.client = client;
} }
public BluetoothDownloader(BluetoothClient client, File destFile, Context ctx) throws IOException { BluetoothDownloader(BluetoothClient client, OutputStream output) throws MalformedURLException {
super(destFile, ctx);
this.client = client;
}
public BluetoothDownloader(BluetoothClient client, OutputStream output) throws MalformedURLException {
super(output); super(output);
this.client = client;
} }
@Override @Override
public InputStream inputStream() throws IOException { public InputStream getInputStream() throws IOException {
Response response = new Request(Request.Methods.GET, client).send(); Response response = Request.createGET(sourceUrl.getPath(), client.openConnection()).send();
fileDetails = response.toFileDetails(); fileDetails = response.toFileDetails();
return response.toContentStream(); return response.toContentStream();
} }
@ -58,7 +54,7 @@ public class BluetoothDownloader extends Downloader {
if (fileDetails == null) { if (fileDetails == null) {
Log.d(TAG, "Going to Bluetooth \"server\" to get file details."); Log.d(TAG, "Going to Bluetooth \"server\" to get file details.");
try { try {
fileDetails = new Request(Request.Methods.HEAD, client).send().toFileDetails(); fileDetails = Request.createHEAD(sourceUrl.getPath(), client.openConnection()).send().toFileDetails();
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, "Error getting file details from Bluetooth \"server\": " + e.getMessage()); Log.e(TAG, "Error getting file details from Bluetooth \"server\": " + e.getMessage());
} }

View File

@ -2,22 +2,18 @@ package org.fdroid.fdroid.net.bluetooth;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.util.Log;
import org.fdroid.fdroid.Utils;
import java.io.*; import java.io.IOException;
import java.util.UUID;
public class BluetoothClient { public class BluetoothClient {
private static final String TAG = "org.fdroid.fdroid.net.bluetooth.BluetoothClient"; private static final String TAG = "org.fdroid.fdroid.net.bluetooth.BluetoothClient";
private BluetoothAdapter adapter; private final BluetoothAdapter adapter;
private BluetoothDevice device; private BluetoothDevice device;
public BluetoothClient(BluetoothAdapter adapter) { public BluetoothClient() {
this.adapter = adapter; this.adapter = BluetoothAdapter.getDefaultAdapter();
} }
public void pairWithDevice() throws IOException { public void pairWithDevice() throws IOException {
@ -27,55 +23,15 @@ public class BluetoothClient {
} }
// TODO: Don't just take a random bluetooth device :) // TODO: Don't just take a random bluetooth device :)
device = adapter.getBondedDevices().iterator().next(); device = adapter.getBondedDevices().iterator().next();
device.createRfcommSocketToServiceRecord(BluetoothConstants.fdroidUuid());
} }
public Connection openConnection() throws IOException { public BluetoothConnection openConnection() throws IOException {
return new Connection(); 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();
}
}
} }

View File

@ -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();
}
}

View File

@ -1,14 +1,16 @@
package org.fdroid.fdroid.net.bluetooth; package org.fdroid.fdroid.net.bluetooth;
import java.util.UUID;
/** /**
* We need some shared information between the client and the server app. * We need some shared information between the client and the server app.
*/ */
public class BluetoothConstants { public class BluetoothConstants {
public static String fdroidUuid() { public static UUID fdroidUuid() {
// TODO: Generate a UUID deterministically from, e.g. "org.fdroid.fdroid.net.Bluetooth"; // 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 // 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");
} }
} }

View File

@ -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<Connection> clients = new ArrayList<Connection>();
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.");
}
}
}

View File

@ -1,37 +1,53 @@
package org.fdroid.fdroid.net.bluetooth.httpish; 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.io.*;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale;
import java.util.Map; import java.util.Map;
public class Request { public class Request {
public static interface Methods { public static interface Methods {
public static final String HEAD = "HEAD"; public static final String HEAD = "HEAD";
public static final String GET = "GET"; public static final String GET = "GET";
} }
private final BluetoothClient client; private String method;
private final String method; private String path;
private Map<String, String> headers;
private BluetoothClient.Connection connection; private BluetoothConnection connection;
private BufferedWriter output; private BufferedWriter output;
private BufferedReader input; private BufferedReader input;
public Request(String method, BluetoothClient client) { private Request(String method, String path, BluetoothConnection connection) {
this.method = method; 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 { public Response send() throws IOException {
connection = client.openConnection();
output = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream())); output = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));
input = new BufferedReader(new InputStreamReader(connection.getInputStream())); input = new BufferedReader(new InputStreamReader(connection.getInputStream()));
output.write(method); output.write(method);
output.write(' ');
output.write(path);
output.write("\n\n");
int responseCode = readResponseCode(); int responseCode = readResponseCode();
Map<String, String> headers = readHeaders(); Map<String, String> 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: * First line of a HTTP response is the status line:
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1 * http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1

View File

@ -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<BluetoothDevice> {
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<BluetoothDevice> objects) {
super(context, resource, objects);
}
public Adapter(Context context, int resource, int textViewResourceId, List<BluetoothDevice> 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;
}
}