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_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" />
|
||||||
|
@ -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"
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
@ -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
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.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());
|
||||||
}
|
}
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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;
|
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
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;
|
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
|
||||||
|
@ -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