WIP: Bluetooth list of paired devices.
This commit is contained in:
parent
45a3efa2b3
commit
fba02e32b5
@ -44,6 +44,7 @@
|
||||
<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.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="18" />
|
||||
|
@ -25,12 +25,12 @@
|
||||
android:layout_below="@+id/text_description"
|
||||
android:layout_centerHorizontal="true" />
|
||||
|
||||
<!--
|
||||
<Button style="@style/SwapTheme.Wizard.OptionButton"
|
||||
android:id="@+id/btn_bluetooth"
|
||||
android:text="@string/swap_use_bluetooth"
|
||||
android:layout_alignParentBottom="true" />
|
||||
|
||||
<!--
|
||||
<Button style="@style/SwapTheme.Wizard.OptionButton"
|
||||
android:text="@string/swap_wifi_help"
|
||||
android:layout_above="@id/btn_bluetooth"
|
||||
|
@ -43,6 +43,17 @@
|
||||
<item name="android:background">@color/white</item>
|
||||
</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>
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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<Void, String, Void> {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
|
@ -9,4 +9,5 @@ package org.fdroid.fdroid.views.swap;
|
||||
public interface SwapProcessManager {
|
||||
void nextStep();
|
||||
void stopSwapping();
|
||||
void connectWithBluetooth();
|
||||
}
|
||||
|
21
bluetooth-notes.txt
Normal file
21
bluetooth-notes.txt
Normal 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
|
||||
|
29
res/layout/swap_bluetooth_header.xml
Normal file
29
res/layout/swap_bluetooth_header.xml
Normal 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>
|
@ -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());
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
61
src/org/fdroid/fdroid/net/bluetooth/BluetoothConnection.java
Normal file
61
src/org/fdroid/fdroid/net/bluetooth/BluetoothConnection.java
Normal 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();
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
112
src/org/fdroid/fdroid/net/bluetooth/BluetoothServer.java
Normal file
112
src/org/fdroid/fdroid/net/bluetooth/BluetoothServer.java
Normal 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.");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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<String, String> 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<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:
|
||||
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user