WIP: Bluetooth communication between devices is up and running (not finished).
Devices now make themselves discoverable, and the client sends a test ping. They UI is not styles properly though, and it doesn't handle the case where somebody chooses to make their device not-discoverable (because the desired peer is already paired and it is unneccesary). It also doesn't handle failure anywhere.
This commit is contained in:
parent
fba02e32b5
commit
7dff9a9499
@ -456,7 +456,8 @@
|
|||||||
|
|
||||||
<service android:name=".UpdateService" />
|
<service android:name=".UpdateService" />
|
||||||
<service android:name=".net.WifiStateChangeService" />
|
<service android:name=".net.WifiStateChangeService" />
|
||||||
<service android:name=".localrepo.LocalRepoService" />
|
<service android:name=".localrepo.LocalRepoWifiService" />
|
||||||
|
<service android:name=".localrepo.LocalRepoProxyService" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -43,15 +43,20 @@
|
|||||||
<item name="android:background">@color/white</item>
|
<item name="android:background">@color/white</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="SwapTheme.BluetoothDeviceList" parent="AppThemeLightWithDarkActionBar">
|
<style name="SwapTheme.StartSwap.Text" parent="@style/SwapTheme.StartSwap">
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="SwapTheme.BluetoothDeviceList" parent="@style/SwapTheme.StartSwap">
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="SwapTheme.BluetoothDeviceList.ListItem" parent="AppThemeLightWithDarkActionBar">
|
<style name="SwapTheme.BluetoothDeviceList.ListItem" parent="AppThemeLightWithDarkActionBar">
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="SwapTheme.BluetoothDeviceList.Heading" parent="@style/SwapTheme.Wizard.MainText">
|
<style name="SwapTheme.BluetoothDeviceList.Text" parent="@style/SwapTheme.BluetoothDeviceList">
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="SwapTheme.BluetoothDeviceList.Heading" parent="@style/SwapTheme.BluetoothDeviceList.Text">
|
||||||
<item name="android:textSize">32.5dp</item> <!-- 58px * 96dpi / 160dpi = 32.5sp -->
|
<item name="android:textSize">32.5dp</item> <!-- 58px * 96dpi / 160dpi = 32.5sp -->
|
||||||
<item name="android:textColor">#222</item>
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="SwapTheme.AppList" parent="AppThemeLightWithDarkActionBar">
|
<style name="SwapTheme.AppList" parent="AppThemeLightWithDarkActionBar">
|
||||||
|
@ -52,7 +52,9 @@ import org.fdroid.fdroid.compat.PRNGFixes;
|
|||||||
import org.fdroid.fdroid.data.AppProvider;
|
import org.fdroid.fdroid.data.AppProvider;
|
||||||
import org.fdroid.fdroid.data.InstalledAppCacheUpdater;
|
import org.fdroid.fdroid.data.InstalledAppCacheUpdater;
|
||||||
import org.fdroid.fdroid.data.Repo;
|
import org.fdroid.fdroid.data.Repo;
|
||||||
|
import org.fdroid.fdroid.localrepo.LocalRepoProxyService;
|
||||||
import org.fdroid.fdroid.localrepo.LocalRepoService;
|
import org.fdroid.fdroid.localrepo.LocalRepoService;
|
||||||
|
import org.fdroid.fdroid.localrepo.LocalRepoWifiService;
|
||||||
import org.fdroid.fdroid.net.IconDownloader;
|
import org.fdroid.fdroid.net.IconDownloader;
|
||||||
import org.fdroid.fdroid.net.WifiStateChangeService;
|
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||||
|
|
||||||
@ -73,8 +75,6 @@ public class FDroidApp extends Application {
|
|||||||
|
|
||||||
// Leaving the fully qualified class name here to help clarify the difference between spongy/bouncy castle.
|
// Leaving the fully qualified class name here to help clarify the difference between spongy/bouncy castle.
|
||||||
private static final org.spongycastle.jce.provider.BouncyCastleProvider spongyCastleProvider;
|
private static final org.spongycastle.jce.provider.BouncyCastleProvider spongyCastleProvider;
|
||||||
private static Messenger localRepoServiceMessenger = null;
|
|
||||||
private static boolean localRepoServiceIsBound = false;
|
|
||||||
|
|
||||||
private static final String TAG = "FDroidApp";
|
private static final String TAG = "FDroidApp";
|
||||||
|
|
||||||
@ -91,6 +91,9 @@ public class FDroidApp extends Application {
|
|||||||
|
|
||||||
private static Theme curTheme = Theme.dark;
|
private static Theme curTheme = Theme.dark;
|
||||||
|
|
||||||
|
public static final LocalRepoServiceManager localRepoWifi = new LocalRepoServiceManager(LocalRepoWifiService.class);
|
||||||
|
public static final LocalRepoServiceManager localRepoProxy = new LocalRepoServiceManager(LocalRepoProxyService.class);
|
||||||
|
|
||||||
public void reloadTheme() {
|
public void reloadTheme() {
|
||||||
curTheme = Theme.valueOf(PreferenceManager
|
curTheme = Theme.valueOf(PreferenceManager
|
||||||
.getDefaultSharedPreferences(getBaseContext())
|
.getDefaultSharedPreferences(getBaseContext())
|
||||||
@ -302,52 +305,72 @@ public class FDroidApp extends Application {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final ServiceConnection serviceConnection = new ServiceConnection() {
|
|
||||||
@Override
|
|
||||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
|
||||||
localRepoServiceMessenger = new Messenger(service);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceDisconnected(ComponentName className) {
|
|
||||||
localRepoServiceMessenger = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static void startLocalRepoService(Context context) {
|
|
||||||
if (!localRepoServiceIsBound) {
|
|
||||||
Context app = context.getApplicationContext();
|
|
||||||
Intent service = new Intent(app, LocalRepoService.class);
|
|
||||||
localRepoServiceIsBound = app.bindService(service, serviceConnection, Context.BIND_AUTO_CREATE);
|
|
||||||
if (localRepoServiceIsBound)
|
|
||||||
app.startService(service);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void stopLocalRepoService(Context context) {
|
|
||||||
Context app = context.getApplicationContext();
|
|
||||||
if (localRepoServiceIsBound) {
|
|
||||||
app.unbindService(serviceConnection);
|
|
||||||
localRepoServiceIsBound = false;
|
|
||||||
}
|
|
||||||
app.stopService(new Intent(app, LocalRepoService.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles checking if the {@link LocalRepoService} is running, and only restarts it if it was running.
|
* Helper class to encapsulate functionality relating to local repo service starting/stopping/restarting.
|
||||||
|
* It used to live as static methods in FDroidApp, but once there were two types of local repos which
|
||||||
|
* could get started (wifi and local proxy for bluetooth) then it got a bit messy. This allows us to
|
||||||
|
* support managing both of these services through two static variables
|
||||||
|
* {@link org.fdroid.fdroid.FDroidApp#localRepoProxy} and {@link org.fdroid.fdroid.FDroidApp#localRepoWifi}.
|
||||||
*/
|
*/
|
||||||
public static void restartLocalRepoServiceIfRunning() {
|
public static class LocalRepoServiceManager {
|
||||||
if (localRepoServiceMessenger != null) {
|
|
||||||
try {
|
private Messenger localRepoServiceMessenger = null;
|
||||||
Message msg = Message.obtain(null, LocalRepoService.RESTART, LocalRepoService.RESTART, 0);
|
private boolean localRepoServiceIsBound = false;
|
||||||
localRepoServiceMessenger.send(msg);
|
|
||||||
} catch (RemoteException e) {
|
private final Class<?extends LocalRepoService> serviceType;
|
||||||
e.printStackTrace();
|
|
||||||
|
public LocalRepoServiceManager(Class<?extends LocalRepoService> serviceType) {
|
||||||
|
this.serviceType = serviceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServiceConnection serviceConnection = new ServiceConnection() {
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||||
|
localRepoServiceMessenger = new Messenger(service);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName className) {
|
||||||
|
localRepoServiceMessenger = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public void start(Context context) {
|
||||||
|
if (!localRepoServiceIsBound) {
|
||||||
|
Context app = context.getApplicationContext();
|
||||||
|
Intent service = new Intent(app, serviceType);
|
||||||
|
localRepoServiceIsBound = app.bindService(service, serviceConnection, Context.BIND_AUTO_CREATE);
|
||||||
|
if (localRepoServiceIsBound)
|
||||||
|
app.startService(service);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isLocalRepoServiceRunning() {
|
public void stop(Context context) {
|
||||||
return localRepoServiceIsBound;
|
Context app = context.getApplicationContext();
|
||||||
|
if (localRepoServiceIsBound) {
|
||||||
|
app.unbindService(serviceConnection);
|
||||||
|
localRepoServiceIsBound = false;
|
||||||
|
}
|
||||||
|
app.stopService(new Intent(app, serviceType));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles checking if the {@link LocalRepoService} is running, and only restarts it if it was running.
|
||||||
|
*/
|
||||||
|
public void restartIfRunning() {
|
||||||
|
if (localRepoServiceMessenger != null) {
|
||||||
|
try {
|
||||||
|
Message msg = Message.obtain(null, LocalRepoService.RESTART, LocalRepoService.RESTART, 0);
|
||||||
|
localRepoServiceMessenger.send(msg);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isRunning() {
|
||||||
|
return localRepoServiceIsBound;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,7 @@ import android.app.Notification;
|
|||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
@ -20,8 +16,6 @@ import android.support.v4.content.LocalBroadcastManager;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
import org.fdroid.fdroid.Preferences;
|
|
||||||
import org.fdroid.fdroid.Preferences.ChangeListener;
|
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
import org.fdroid.fdroid.net.LocalHTTPD;
|
import org.fdroid.fdroid.net.LocalHTTPD;
|
||||||
@ -30,13 +24,13 @@ import org.fdroid.fdroid.views.swap.SwapActivity;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.BindException;
|
import java.net.BindException;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import javax.jmdns.JmDNS;
|
import javax.jmdns.JmDNS;
|
||||||
import javax.jmdns.ServiceInfo;
|
import javax.jmdns.ServiceInfo;
|
||||||
|
|
||||||
public class LocalRepoService extends Service {
|
public abstract class LocalRepoService extends Service {
|
||||||
|
|
||||||
private static final String TAG = "LocalRepoService";
|
private static final String TAG = "LocalRepoService";
|
||||||
|
|
||||||
public static final String STATE = "org.fdroid.fdroid.action.LOCAL_REPO_STATE";
|
public static final String STATE = "org.fdroid.fdroid.action.LOCAL_REPO_STATE";
|
||||||
@ -50,9 +44,7 @@ public class LocalRepoService extends Service {
|
|||||||
private final int NOTIFICATION = R.string.local_repo_running;
|
private final int NOTIFICATION = R.string.local_repo_running;
|
||||||
|
|
||||||
private Handler webServerThreadHandler = null;
|
private Handler webServerThreadHandler = null;
|
||||||
private LocalHTTPD localHttpd;
|
protected LocalHTTPD localHttpd;
|
||||||
private JmDNS jmdns;
|
|
||||||
private ServiceInfo pairService;
|
|
||||||
|
|
||||||
public static final int START = 1111111;
|
public static final int START = 1111111;
|
||||||
public static final int STOP = 12345678;
|
public static final int STOP = 12345678;
|
||||||
@ -97,42 +89,6 @@ public class LocalRepoService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final BroadcastReceiver onWifiChange = new BroadcastReceiver() {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent i) {
|
|
||||||
stopNetworkServices();
|
|
||||||
startNetworkServices();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private ChangeListener localRepoBonjourChangeListener = new ChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onPreferenceChange() {
|
|
||||||
if (localHttpd.isAlive())
|
|
||||||
if (Preferences.get().isLocalRepoBonjourEnabled())
|
|
||||||
registerMDNSService();
|
|
||||||
else
|
|
||||||
unregisterMDNSService();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final ChangeListener localRepoHttpsChangeListener = new ChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void onPreferenceChange() {
|
|
||||||
Log.i(TAG, "onPreferenceChange");
|
|
||||||
if (localHttpd.isAlive()) {
|
|
||||||
new AsyncTask<Void, Void, Void>() {
|
|
||||||
@Override
|
|
||||||
protected Void doInBackground(Void... params) {
|
|
||||||
stopNetworkServices();
|
|
||||||
startNetworkServices();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}.execute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private void showNotification() {
|
private void showNotification() {
|
||||||
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||||
// launch LocalRepoActivity if the user selects this notification
|
// launch LocalRepoActivity if the user selects this notification
|
||||||
@ -150,12 +106,10 @@ public class LocalRepoService extends Service {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
|
||||||
showNotification();
|
showNotification();
|
||||||
startNetworkServices();
|
startNetworkServices();
|
||||||
Preferences.get().registerLocalRepoBonjourListeners(localRepoBonjourChangeListener);
|
|
||||||
|
|
||||||
LocalBroadcastManager.getInstance(this).registerReceiver(onWifiChange,
|
|
||||||
new IntentFilter(WifiStateChangeService.BROADCAST));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -167,6 +121,7 @@ public class LocalRepoService extends Service {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroy() {
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
new Thread() {
|
new Thread() {
|
||||||
public void run() {
|
public void run() {
|
||||||
stopNetworkServices();
|
stopNetworkServices();
|
||||||
@ -174,8 +129,6 @@ public class LocalRepoService extends Service {
|
|||||||
}.start();
|
}.start();
|
||||||
|
|
||||||
notificationManager.cancel(NOTIFICATION);
|
notificationManager.cancel(NOTIFICATION);
|
||||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(onWifiChange);
|
|
||||||
Preferences.get().unregisterLocalRepoBonjourListeners(localRepoBonjourChangeListener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -183,26 +136,40 @@ public class LocalRepoService extends Service {
|
|||||||
return messenger.getBinder();
|
return messenger.getBinder();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startNetworkServices() {
|
/**
|
||||||
|
* Called immediately _after_ the webserver is started.
|
||||||
|
*/
|
||||||
|
protected abstract void onStartNetworkServices();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called immediately _before_ the webserver is stopped.
|
||||||
|
*/
|
||||||
|
protected abstract void onStopNetworkServices();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not this particular version of LocalRepoService requires a HTTPS
|
||||||
|
* connection. In the local proxy instance, it will not require it, but in the
|
||||||
|
* wifi setting, it should use whatever preference the user selected.
|
||||||
|
*/
|
||||||
|
protected abstract boolean useHttps();
|
||||||
|
|
||||||
|
protected void startNetworkServices() {
|
||||||
Log.d(TAG, "Starting local repo network services");
|
Log.d(TAG, "Starting local repo network services");
|
||||||
startWebServer();
|
startWebServer();
|
||||||
if (Preferences.get().isLocalRepoBonjourEnabled())
|
|
||||||
registerMDNSService();
|
onStartNetworkServices();
|
||||||
Preferences.get().registerLocalRepoHttpsListeners(localRepoHttpsChangeListener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopNetworkServices() {
|
protected void stopNetworkServices() {
|
||||||
Log.d(TAG, "Stopping local repo network services");
|
onStopNetworkServices();
|
||||||
Preferences.get().unregisterLocalRepoHttpsListeners(localRepoHttpsChangeListener);
|
|
||||||
|
|
||||||
Log.d(TAG, "Unregistering MDNS service...");
|
|
||||||
unregisterMDNSService();
|
|
||||||
|
|
||||||
Log.d(TAG, "Stopping web server...");
|
Log.d(TAG, "Stopping web server...");
|
||||||
stopWebServer();
|
stopWebServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startWebServer() {
|
protected abstract String getIpAddressToBindTo();
|
||||||
|
|
||||||
|
protected void startWebServer() {
|
||||||
Runnable webServer = new Runnable() {
|
Runnable webServer = new Runnable() {
|
||||||
// Tell Eclipse this is not a leak because of Looper use.
|
// Tell Eclipse this is not a leak because of Looper use.
|
||||||
@SuppressLint("HandlerLeak")
|
@SuppressLint("HandlerLeak")
|
||||||
@ -210,8 +177,10 @@ public class LocalRepoService extends Service {
|
|||||||
public void run() {
|
public void run() {
|
||||||
localHttpd = new LocalHTTPD(
|
localHttpd = new LocalHTTPD(
|
||||||
LocalRepoService.this,
|
LocalRepoService.this,
|
||||||
|
getIpAddressToBindTo(),
|
||||||
|
FDroidApp.port,
|
||||||
getFilesDir(),
|
getFilesDir(),
|
||||||
Preferences.get().isLocalRepoHttpsEnabled());
|
useHttps());
|
||||||
|
|
||||||
Looper.prepare(); // must be run before creating a Handler
|
Looper.prepare(); // must be run before creating a Handler
|
||||||
webServerThreadHandler = new Handler() {
|
webServerThreadHandler = new Handler() {
|
||||||
@ -254,59 +223,5 @@ public class LocalRepoService extends Service {
|
|||||||
LocalBroadcastManager.getInstance(LocalRepoService.this).sendBroadcast(intent);
|
LocalBroadcastManager.getInstance(LocalRepoService.this).sendBroadcast(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerMDNSService() {
|
|
||||||
new Thread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
/*
|
|
||||||
* a ServiceInfo can only be registered with a single instance
|
|
||||||
* of JmDNS, and there is only ever a single LocalHTTPD port to
|
|
||||||
* advertise anyway.
|
|
||||||
*/
|
|
||||||
if (pairService != null || jmdns != null)
|
|
||||||
clearCurrentMDNSService();
|
|
||||||
String repoName = Preferences.get().getLocalRepoName();
|
|
||||||
HashMap<String, String> values = new HashMap<>();
|
|
||||||
values.put("path", "/fdroid/repo");
|
|
||||||
values.put("name", repoName);
|
|
||||||
values.put("fingerprint", FDroidApp.repo.fingerprint);
|
|
||||||
String type;
|
|
||||||
if (Preferences.get().isLocalRepoHttpsEnabled()) {
|
|
||||||
values.put("type", "fdroidrepos");
|
|
||||||
type = "_https._tcp.local.";
|
|
||||||
} else {
|
|
||||||
values.put("type", "fdroidrepo");
|
|
||||||
type = "_http._tcp.local.";
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
pairService = ServiceInfo.create(type, repoName, FDroidApp.port, 0, 0, values);
|
|
||||||
jmdns = JmDNS.create();
|
|
||||||
jmdns.registerService(pairService);
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(TAG, "Error while registering jmdns service: " + e);
|
|
||||||
Log.e(TAG, Log.getStackTraceString(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void unregisterMDNSService() {
|
|
||||||
if (localRepoBonjourChangeListener != null) {
|
|
||||||
Preferences.get().unregisterLocalRepoBonjourListeners(localRepoBonjourChangeListener);
|
|
||||||
localRepoBonjourChangeListener = null;
|
|
||||||
}
|
|
||||||
clearCurrentMDNSService();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearCurrentMDNSService() {
|
|
||||||
if (jmdns != null) {
|
|
||||||
if (pairService != null) {
|
|
||||||
jmdns.unregisterService(pairService);
|
|
||||||
pairService = null;
|
|
||||||
}
|
|
||||||
jmdns.unregisterAllServices();
|
|
||||||
Utils.closeQuietly(jmdns);
|
|
||||||
jmdns = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,9 +2,9 @@ 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.Preferences;
|
import org.fdroid.fdroid.Preferences;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLHandshakeException;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -16,8 +16,6 @@ import java.net.Proxy;
|
|||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
import javax.net.ssl.SSLHandshakeException;
|
|
||||||
|
|
||||||
public class HttpDownloader extends Downloader {
|
public class HttpDownloader extends Downloader {
|
||||||
private static final String TAG = "HttpDownloader";
|
private static final String TAG = "HttpDownloader";
|
||||||
|
|
||||||
@ -26,6 +24,7 @@ public class HttpDownloader extends Downloader {
|
|||||||
|
|
||||||
protected HttpURLConnection connection;
|
protected HttpURLConnection connection;
|
||||||
private int statusCode = -1;
|
private int statusCode = -1;
|
||||||
|
private boolean onlyStream = false;
|
||||||
|
|
||||||
// The context is required for opening the file to write to.
|
// The context is required for opening the file to write to.
|
||||||
HttpDownloader(String source, File destFile)
|
HttpDownloader(String source, File destFile)
|
||||||
@ -39,11 +38,22 @@ public class HttpDownloader extends Downloader {
|
|||||||
* you are done*.
|
* you are done*.
|
||||||
* @see org.fdroid.fdroid.net.Downloader#getFile()
|
* @see org.fdroid.fdroid.net.Downloader#getFile()
|
||||||
*/
|
*/
|
||||||
HttpDownloader(String source, Context ctx) throws IOException {
|
public HttpDownloader(String source, Context ctx) throws IOException {
|
||||||
super(ctx);
|
super(ctx);
|
||||||
sourceUrl = new URL(source);
|
sourceUrl = new URL(source);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calling this makes this downloader not download a file. Instead, it will
|
||||||
|
* only stream the file through the {@link HttpDownloader#getInputStream()}
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public HttpDownloader streamDontDownload()
|
||||||
|
{
|
||||||
|
onlyStream = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InputStream getInputStream() throws IOException {
|
public InputStream getInputStream() throws IOException {
|
||||||
setupConnection();
|
setupConnection();
|
||||||
|
@ -36,8 +36,8 @@ public class LocalHTTPD extends NanoHTTPD {
|
|||||||
private final File webRoot;
|
private final File webRoot;
|
||||||
private final boolean logRequests;
|
private final boolean logRequests;
|
||||||
|
|
||||||
public LocalHTTPD(Context context, File webRoot, boolean useHttps) {
|
public LocalHTTPD(Context context, String hostname, int port, File webRoot, boolean useHttps) {
|
||||||
super(FDroidApp.ipAddressString, FDroidApp.port);
|
super(hostname, port);
|
||||||
this.logRequests = false;
|
this.logRequests = false;
|
||||||
this.webRoot = webRoot;
|
this.webRoot = webRoot;
|
||||||
this.context = context.getApplicationContext();
|
this.context = context.getApplicationContext();
|
||||||
|
@ -151,7 +151,7 @@ public class WifiStateChangeService extends Service {
|
|||||||
Intent intent = new Intent(BROADCAST);
|
Intent intent = new Intent(BROADCAST);
|
||||||
LocalBroadcastManager.getInstance(WifiStateChangeService.this).sendBroadcast(intent);
|
LocalBroadcastManager.getInstance(WifiStateChangeService.this).sendBroadcast(intent);
|
||||||
WifiStateChangeService.this.stopSelf();
|
WifiStateChangeService.this.stopSelf();
|
||||||
FDroidApp.restartLocalRepoServiceIfRunning();
|
FDroidApp.localRepoWifi.restartIfRunning();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ public class LocalRepoActivity extends ActionBarActivity {
|
|||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
resetNetworkInfo();
|
resetNetworkInfo();
|
||||||
setRepoSwitchChecked(FDroidApp.isLocalRepoServiceRunning());
|
setRepoSwitchChecked(FDroidApp.localRepoWifi.isRunning());
|
||||||
|
|
||||||
LocalBroadcastManager.getInstance(this).registerReceiver(onWifiChange,
|
LocalBroadcastManager.getInstance(this).registerReceiver(onWifiChange,
|
||||||
new IntentFilter(WifiStateChangeService.BROADCAST));
|
new IntentFilter(WifiStateChangeService.BROADCAST));
|
||||||
@ -83,7 +83,7 @@ public class LocalRepoActivity extends ActionBarActivity {
|
|||||||
}).execute();
|
}).execute();
|
||||||
|
|
||||||
// start repo by default
|
// start repo by default
|
||||||
FDroidApp.startLocalRepoService(LocalRepoActivity.this);
|
FDroidApp.localRepoWifi.start(LocalRepoActivity.this);
|
||||||
// reset the timer if viewing this Activity again
|
// reset the timer if viewing this Activity again
|
||||||
if (stopTimer != null)
|
if (stopTimer != null)
|
||||||
stopTimer.cancel();
|
stopTimer.cancel();
|
||||||
@ -93,7 +93,7 @@ public class LocalRepoActivity extends ActionBarActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
FDroidApp.stopLocalRepoService(LocalRepoActivity.this);
|
FDroidApp.localRepoWifi.stop(LocalRepoActivity.this);
|
||||||
}
|
}
|
||||||
}, 900000); // 15 minutes
|
}, 900000); // 15 minutes
|
||||||
}
|
}
|
||||||
@ -210,9 +210,9 @@ public class LocalRepoActivity extends ActionBarActivity {
|
|||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
setRepoSwitchChecked(repoSwitch.isChecked());
|
setRepoSwitchChecked(repoSwitch.isChecked());
|
||||||
if (repoSwitch.isChecked()) {
|
if (repoSwitch.isChecked()) {
|
||||||
FDroidApp.startLocalRepoService(LocalRepoActivity.this);
|
FDroidApp.localRepoWifi.start(LocalRepoActivity.this);
|
||||||
} else {
|
} else {
|
||||||
FDroidApp.stopLocalRepoService(LocalRepoActivity.this);
|
FDroidApp.localRepoWifi.stop(LocalRepoActivity.this);
|
||||||
stopTimer.cancel(); // disable automatic stop
|
stopTimer.cancel(); // disable automatic stop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ public class QrWizardWifiNetworkActivity extends ActionBarActivity {
|
|||||||
|
|
||||||
wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
|
wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
|
||||||
wifiManager.setWifiEnabled(true);
|
wifiManager.setWifiEnabled(true);
|
||||||
FDroidApp.startLocalRepoService(this);
|
FDroidApp.localRepoWifi.start(this);
|
||||||
|
|
||||||
setContentView(R.layout.qr_wizard_activity);
|
setContentView(R.layout.qr_wizard_activity);
|
||||||
TextView instructions = (TextView) findViewById(R.id.qrWizardInstructions);
|
TextView instructions = (TextView) findViewById(R.id.qrWizardInstructions);
|
||||||
|
@ -21,9 +21,18 @@ public abstract class ThemeableListFragment extends ListFragment {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected View getHeaderView(LayoutInflater inflater, ViewGroup container) {
|
protected View getHeaderView() {
|
||||||
|
return headerView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private View headerView = null;
|
||||||
|
|
||||||
|
private View getHeaderView(LayoutInflater inflater, ViewGroup container) {
|
||||||
if (getHeaderLayout() > 0) {
|
if (getHeaderLayout() > 0) {
|
||||||
return inflater.inflate(getHeaderLayout(), null, false);
|
if (headerView == null) {
|
||||||
|
headerView = inflater.inflate(getHeaderLayout(), null, false);
|
||||||
|
}
|
||||||
|
return headerView;
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,8 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
|
|||||||
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 String STATE_BLUETOOTH_DEVICE_LIST = "bluetoothDeviceList";
|
||||||
|
|
||||||
private static final int REQUEST_ENABLE_BLUETOOTH = 1;
|
private static final int REQUEST_BLUETOOTH_ENABLE = 1;
|
||||||
|
private static final int REQUEST_BLUETOOTH_DISCOVERABLE = 2;
|
||||||
|
|
||||||
private static final String TAG = "org.fdroid.fdroid.views.swap.SwapActivity";
|
private static final String TAG = "org.fdroid.fdroid.views.swap.SwapActivity";
|
||||||
|
|
||||||
@ -109,7 +110,7 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
|
|||||||
|
|
||||||
showFragment(new StartSwapFragment(), STATE_START_SWAP);
|
showFragment(new StartSwapFragment(), STATE_START_SWAP);
|
||||||
|
|
||||||
if (FDroidApp.isLocalRepoServiceRunning()) {
|
if (FDroidApp.localRepoWifi.isRunning()) {
|
||||||
showSelectApps();
|
showSelectApps();
|
||||||
showJoinWifi();
|
showJoinWifi();
|
||||||
attemptToShowNfc();
|
attemptToShowNfc();
|
||||||
@ -190,8 +191,8 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void ensureLocalRepoRunning() {
|
private void ensureLocalRepoRunning() {
|
||||||
if (!FDroidApp.isLocalRepoServiceRunning()) {
|
if (!FDroidApp.localRepoWifi.isRunning()) {
|
||||||
FDroidApp.startLocalRepoService(this);
|
FDroidApp.localRepoWifi.start(this);
|
||||||
initLocalRepoTimer(900000); // 15 mins
|
initLocalRepoTimer(900000); // 15 mins
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -207,7 +208,7 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
|
|||||||
shutdownLocalRepoTimer.schedule(new TimerTask() {
|
shutdownLocalRepoTimer.schedule(new TimerTask() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
FDroidApp.stopLocalRepoService(SwapActivity.this);
|
FDroidApp.localRepoWifi.stop(SwapActivity.this);
|
||||||
}
|
}
|
||||||
}, timeoutMilliseconds);
|
}, timeoutMilliseconds);
|
||||||
|
|
||||||
@ -215,11 +216,11 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stopSwapping() {
|
public void stopSwapping() {
|
||||||
if (FDroidApp.isLocalRepoServiceRunning()) {
|
if (FDroidApp.localRepoWifi.isRunning()) {
|
||||||
if (shutdownLocalRepoTimer != null) {
|
if (shutdownLocalRepoTimer != null) {
|
||||||
shutdownLocalRepoTimer.cancel();
|
shutdownLocalRepoTimer.cancel();
|
||||||
}
|
}
|
||||||
FDroidApp.stopLocalRepoService(SwapActivity.this);
|
FDroidApp.localRepoWifi.stop(SwapActivity.this);
|
||||||
}
|
}
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
@ -242,11 +243,11 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
|
|||||||
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
|
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
|
||||||
if (adapter.isEnabled()) {
|
if (adapter.isEnabled()) {
|
||||||
Log.d(TAG, "Bluetooth enabled, will pair with device.");
|
Log.d(TAG, "Bluetooth enabled, will pair with device.");
|
||||||
startBluetoothServer();
|
ensureBluetoothDiscoverable();
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Bluetooth disabled, asking user to enable it.");
|
Log.d(TAG, "Bluetooth disabled, asking user to enable it.");
|
||||||
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
|
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
|
||||||
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BLUETOOTH);
|
startActivityForResult(enableBtIntent, REQUEST_BLUETOOTH_ENABLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -254,22 +255,51 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage
|
|||||||
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
|
||||||
if (requestCode == REQUEST_ENABLE_BLUETOOTH) {
|
if (requestCode == REQUEST_BLUETOOTH_ENABLE) {
|
||||||
|
|
||||||
if (resultCode == RESULT_OK) {
|
if (resultCode == RESULT_OK) {
|
||||||
Log.d(TAG, "User enabled Bluetooth, will pair with device.");
|
Log.d(TAG, "User enabled Bluetooth, will make sure we are discoverable.");
|
||||||
startBluetoothServer();
|
ensureBluetoothDiscoverable();
|
||||||
} else {
|
} else {
|
||||||
// Didn't enable bluetooth
|
// Didn't enable bluetooth
|
||||||
Log.d(TAG, "User chose not to enable Bluetooth, so doing nothing (i.e. sticking with wifi).");
|
Log.d(TAG, "User chose not to enable Bluetooth, so doing nothing (i.e. sticking with wifi).");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else if (requestCode == REQUEST_BLUETOOTH_DISCOVERABLE) {
|
||||||
|
|
||||||
|
if (resultCode != RESULT_CANCELED) {
|
||||||
|
Log.d(TAG, "User made Bluetooth discoverable, will proceed to start bluetooth server.");
|
||||||
|
startBluetoothServer();
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "User chose not to make Bluetooth discoverable, so doing nothing (i.e. sticking with wifi).");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureBluetoothDiscoverable() {
|
||||||
|
Log.d(TAG, "Ensuring Bluetooth is in discoverable mode.");
|
||||||
|
if (BluetoothAdapter.getDefaultAdapter().getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) {
|
||||||
|
|
||||||
|
// TODO: Listen for BluetoothAdapter.ACTION_SCAN_MODE_CHANGED and respond if discovery
|
||||||
|
// is cancelled prematurely.
|
||||||
|
|
||||||
|
Log.d(TAG, "Not currently in discoverable mode, so prompting user to enable.");
|
||||||
|
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
|
||||||
|
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
|
||||||
|
startActivityForResult(intent, REQUEST_BLUETOOTH_DISCOVERABLE);
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Bluetooth is already discoverable, so lets start the Bluetooth server.");
|
||||||
|
startBluetoothServer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startBluetoothServer() {
|
private void startBluetoothServer() {
|
||||||
Log.d(TAG, "Starting bluetooth server.");
|
Log.d(TAG, "Starting bluetooth server.");
|
||||||
new BluetoothServer().start();
|
if (!FDroidApp.localRepoProxy.isRunning()) {
|
||||||
|
FDroidApp.localRepoProxy.start(this);
|
||||||
|
}
|
||||||
|
new BluetoothServer(this).start();
|
||||||
showBluetoothDeviceList();
|
showBluetoothDeviceList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
52
res/layout-v14/simple_list_item_3.xml
Normal file
52
res/layout-v14/simple_list_item_3.xml
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2006 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
This file was modified for F-Droid to include an additional text item beyond
|
||||||
|
simple_list_item_3. Thought we may as well make it as much as possible the
|
||||||
|
same as the original, and so should essentially build on the original one
|
||||||
|
from the Android SDK.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||||
|
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||||
|
>
|
||||||
|
|
||||||
|
<TextView android:id="@android:id/text1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView android:id="@android:id/text2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView android:id="@+id/text3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
52
res/layout/simple_list_item_3.xml
Normal file
52
res/layout/simple_list_item_3.xml
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Copyright (C) 2006 The Android Open Source Project
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
This file was modified for F-Droid to include an additional text item beyond
|
||||||
|
simple_list_item_3. Thought we may as well make it as much as possible the
|
||||||
|
same as the original, and so should essentially build on the original one
|
||||||
|
from the Android SDK.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||||
|
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||||
|
>
|
||||||
|
|
||||||
|
<TextView android:id="@android:id/text1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dip"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView android:id="@android:id/text2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextView android:id="@+id/text3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
@ -9,19 +9,43 @@
|
|||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="394dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/device_ip_address"
|
android:id="@+id/device_name_prefix"
|
||||||
tools:text="Your device name:\nPete's Nexus 4"
|
android:text="Your device is"
|
||||||
style="@style/SwapTheme.BluetoothDeviceList.Heading"/>
|
tools:text="Your device is"
|
||||||
|
android:textAlignment="center"
|
||||||
|
style="@style/SwapTheme.BluetoothDeviceList.Heading" android:paddingTop="10dp"
|
||||||
|
android:paddingBottom="10dp" android:textSize="24sp"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:text="Select from devices below"
|
android:id="@+id/device_name"
|
||||||
style="@style/SwapTheme.Wizard.Text"/>
|
android:text="Pete's Nexus 4"
|
||||||
|
tools:text="Pete's Nexus 4"
|
||||||
|
android:textAlignment="center"
|
||||||
|
style="@style/SwapTheme.BluetoothDeviceList.Heading" android:paddingBottom="10dp"/>
|
||||||
|
|
||||||
<ContentLoadingProgressBar
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/device_address"
|
||||||
|
android:text="(01:02:03:ab:cd:ef)"
|
||||||
|
tools:text="(01:02:03:ab:cd:ef)"
|
||||||
|
android:textAlignment="center"
|
||||||
|
style="@style/SwapTheme.BluetoothDeviceList.Heading" android:paddingBottom="20dp" android:textSize="24sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Select from devices below, or tap here to scan for more devices..."
|
||||||
|
tools:text="Select from devices below, or tap here to scan for more devices..."
|
||||||
|
android:textAlignment="center"
|
||||||
|
style="@style/SwapTheme.BluetoothDeviceList.Text" android:paddingLeft="20dp" android:paddingRight="20dp"
|
||||||
|
android:paddingBottom="10dp"/>
|
||||||
|
|
||||||
|
<android.support.v4.widget.ContentLoadingProgressBar
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:id="@+id/loading_indicator"/>
|
android:id="@+id/loading_indicator"/>
|
||||||
|
29
src/org/fdroid/fdroid/localrepo/LocalRepoProxyService.java
Normal file
29
src/org/fdroid/fdroid/localrepo/LocalRepoProxyService.java
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the local repo service but bound to 127.0.0.1.
|
||||||
|
* Also, it does not care about whether wifi is connected or not,
|
||||||
|
* and thus doesn't care about Bonjour.
|
||||||
|
*/
|
||||||
|
public class LocalRepoProxyService extends LocalRepoService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStartNetworkServices() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStopNetworkServices() {
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean useHttps() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getIpAddressToBindTo() {
|
||||||
|
return "127.0.0.1";
|
||||||
|
}
|
||||||
|
}
|
160
src/org/fdroid/fdroid/localrepo/LocalRepoWifiService.java
Normal file
160
src/org/fdroid/fdroid/localrepo/LocalRepoWifiService.java
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
package org.fdroid.fdroid.localrepo;
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
import android.util.Log;
|
||||||
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
|
import org.fdroid.fdroid.Preferences;
|
||||||
|
import org.fdroid.fdroid.net.WifiStateChangeService;
|
||||||
|
|
||||||
|
import javax.jmdns.JmDNS;
|
||||||
|
import javax.jmdns.ServiceInfo;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
public class LocalRepoWifiService extends LocalRepoService {
|
||||||
|
|
||||||
|
private static final String TAG = "org.fdroid.fdroid.localrepo.LocalRepoWifiService";
|
||||||
|
private JmDNS jmdns;
|
||||||
|
private ServiceInfo pairService;
|
||||||
|
|
||||||
|
private BroadcastReceiver onWifiChange = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent i) {
|
||||||
|
stopNetworkServices();
|
||||||
|
startNetworkServices();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
Preferences.get().registerLocalRepoBonjourListeners(localRepoBonjourChangeListener);
|
||||||
|
LocalBroadcastManager.getInstance(this).registerReceiver(onWifiChange,
|
||||||
|
new IntentFilter(WifiStateChangeService.BROADCAST));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
Preferences.get().unregisterLocalRepoBonjourListeners(localRepoBonjourChangeListener);
|
||||||
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(onWifiChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStartNetworkServices() {
|
||||||
|
if (Preferences.get().isLocalRepoBonjourEnabled())
|
||||||
|
registerMDNSService();
|
||||||
|
Preferences.get().registerLocalRepoHttpsListeners(localRepoHttpsChangeListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStopNetworkServices() {
|
||||||
|
Log.d(TAG, "Stopping local repo network services");
|
||||||
|
Preferences.get().unregisterLocalRepoHttpsListeners(localRepoHttpsChangeListener);
|
||||||
|
|
||||||
|
Log.d(TAG, "Unregistering MDNS service...");
|
||||||
|
unregisterMDNSService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean useHttps() {
|
||||||
|
return Preferences.get().isLocalRepoHttpsEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getIpAddressToBindTo() {
|
||||||
|
return FDroidApp.ipAddressString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Preferences.ChangeListener localRepoBonjourChangeListener = new Preferences.ChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onPreferenceChange() {
|
||||||
|
if (localHttpd.isAlive())
|
||||||
|
if (Preferences.get().isLocalRepoBonjourEnabled())
|
||||||
|
registerMDNSService();
|
||||||
|
else
|
||||||
|
unregisterMDNSService();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private Preferences.ChangeListener localRepoHttpsChangeListener = new Preferences.ChangeListener() {
|
||||||
|
@Override
|
||||||
|
public void onPreferenceChange() {
|
||||||
|
Log.i("localRepoHttpsChangeListener", "onPreferenceChange");
|
||||||
|
if (localHttpd.isAlive()) {
|
||||||
|
new AsyncTask<Void, Void, Void>() {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... params) {
|
||||||
|
stopNetworkServices();
|
||||||
|
startNetworkServices();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private void registerMDNSService() {
|
||||||
|
new Thread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
/*
|
||||||
|
* a ServiceInfo can only be registered with a single instance
|
||||||
|
* of JmDNS, and there is only ever a single LocalHTTPD port to
|
||||||
|
* advertise anyway.
|
||||||
|
*/
|
||||||
|
if (pairService != null || jmdns != null)
|
||||||
|
clearCurrentMDNSService();
|
||||||
|
String repoName = Preferences.get().getLocalRepoName();
|
||||||
|
HashMap<String, String> values = new HashMap<String, String>();
|
||||||
|
values.put("path", "/fdroid/repo");
|
||||||
|
values.put("name", repoName);
|
||||||
|
values.put("fingerprint", FDroidApp.repo.fingerprint);
|
||||||
|
String type;
|
||||||
|
if (Preferences.get().isLocalRepoHttpsEnabled()) {
|
||||||
|
values.put("type", "fdroidrepos");
|
||||||
|
type = "_https._tcp.local.";
|
||||||
|
} else {
|
||||||
|
values.put("type", "fdroidrepo");
|
||||||
|
type = "_http._tcp.local.";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
pairService = ServiceInfo.create(type, repoName, FDroidApp.port, 0, 0, values);
|
||||||
|
jmdns = JmDNS.create();
|
||||||
|
jmdns.registerService(pairService);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unregisterMDNSService() {
|
||||||
|
if (localRepoBonjourChangeListener != null) {
|
||||||
|
Preferences.get().unregisterLocalRepoBonjourListeners(localRepoBonjourChangeListener);
|
||||||
|
localRepoBonjourChangeListener = null;
|
||||||
|
}
|
||||||
|
clearCurrentMDNSService();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearCurrentMDNSService() {
|
||||||
|
if (jmdns != null) {
|
||||||
|
if (pairService != null) {
|
||||||
|
jmdns.unregisterService(pairService);
|
||||||
|
pairService = null;
|
||||||
|
}
|
||||||
|
jmdns.unregisterAllServices();
|
||||||
|
try {
|
||||||
|
jmdns.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
jmdns = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package org.fdroid.fdroid.net.bluetooth;
|
package org.fdroid.fdroid.net.bluetooth;
|
||||||
|
|
||||||
import android.bluetooth.BluetoothAdapter;
|
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.bluetooth.BluetoothSocket;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@ -9,29 +9,17 @@ 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 final BluetoothAdapter adapter;
|
|
||||||
private BluetoothDevice device;
|
private BluetoothDevice device;
|
||||||
|
|
||||||
public BluetoothClient() {
|
public BluetoothClient(BluetoothDevice device) {
|
||||||
this.adapter = BluetoothAdapter.getDefaultAdapter();
|
this.device = device;
|
||||||
}
|
|
||||||
|
|
||||||
public void pairWithDevice() throws IOException {
|
|
||||||
|
|
||||||
if (adapter.getBondedDevices().size() == 0) {
|
|
||||||
throw new IOException("No paired Bluetooth devices.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Don't just take a random bluetooth device :)
|
|
||||||
|
|
||||||
device = adapter.getBondedDevices().iterator().next();
|
|
||||||
device.createRfcommSocketToServiceRecord(BluetoothConstants.fdroidUuid());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BluetoothConnection openConnection() throws IOException {
|
public BluetoothConnection openConnection() throws IOException {
|
||||||
return null;
|
BluetoothSocket socket = device.createRfcommSocketToServiceRecord(BluetoothConstants.fdroidUuid());
|
||||||
// return new BluetoothConnection();
|
BluetoothConnection connection = new BluetoothConnection(socket);
|
||||||
|
connection.open();
|
||||||
|
return connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,12 @@ package org.fdroid.fdroid.net.bluetooth;
|
|||||||
import android.bluetooth.BluetoothAdapter;
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.bluetooth.BluetoothServerSocket;
|
import android.bluetooth.BluetoothServerSocket;
|
||||||
import android.bluetooth.BluetoothSocket;
|
import android.bluetooth.BluetoothSocket;
|
||||||
|
import android.content.Context;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
|
import org.fdroid.fdroid.net.HttpDownloader;
|
||||||
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 java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -20,9 +23,14 @@ public class BluetoothServer extends Thread {
|
|||||||
private static final String TAG = "org.fdroid.fdroid.net.bluetooth.BluetoothServer";
|
private static final String TAG = "org.fdroid.fdroid.net.bluetooth.BluetoothServer";
|
||||||
|
|
||||||
private BluetoothServerSocket serverSocket;
|
private BluetoothServerSocket serverSocket;
|
||||||
|
|
||||||
private List<Connection> clients = new ArrayList<Connection>();
|
private List<Connection> clients = new ArrayList<Connection>();
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
public BluetoothServer(Context context) {
|
||||||
|
this.context = context.getApplicationContext();
|
||||||
|
}
|
||||||
|
|
||||||
public void close() {
|
public void close() {
|
||||||
|
|
||||||
for (Connection connection : clients) {
|
for (Connection connection : clients) {
|
||||||
@ -50,7 +58,7 @@ public class BluetoothServer extends Thread {
|
|||||||
try {
|
try {
|
||||||
BluetoothSocket clientSocket = serverSocket.accept();
|
BluetoothSocket clientSocket = serverSocket.accept();
|
||||||
if (clientSocket != null && !isInterrupted()) {
|
if (clientSocket != null && !isInterrupted()) {
|
||||||
Connection client = new Connection(clientSocket);
|
Connection client = new Connection(context, clientSocket);
|
||||||
client.start();
|
client.start();
|
||||||
clients.add(client);
|
clients.add(client);
|
||||||
} else {
|
} else {
|
||||||
@ -67,9 +75,12 @@ public class BluetoothServer extends Thread {
|
|||||||
{
|
{
|
||||||
|
|
||||||
private static final String TAG = "org.fdroid.fdroid.net.bluetooth.BluetoothServer.Connection";
|
private static final String TAG = "org.fdroid.fdroid.net.bluetooth.BluetoothServer.Connection";
|
||||||
private BluetoothSocket socket;
|
|
||||||
|
|
||||||
public Connection(BluetoothSocket socket) {
|
private final Context context;
|
||||||
|
private final BluetoothSocket socket;
|
||||||
|
|
||||||
|
public Connection(Context context, BluetoothSocket socket) {
|
||||||
|
this.context = context.getApplicationContext();
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +92,7 @@ public class BluetoothServer extends Thread {
|
|||||||
BluetoothConnection connection;
|
BluetoothConnection connection;
|
||||||
try {
|
try {
|
||||||
connection = new BluetoothConnection(socket);
|
connection = new BluetoothConnection(socket);
|
||||||
|
connection.open();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Error listening for incoming connections over bluetooth - " + e.getMessage());
|
Log.e(TAG, "Error listening for incoming connections over bluetooth - " + e.getMessage());
|
||||||
return;
|
return;
|
||||||
@ -91,7 +103,7 @@ public class BluetoothServer extends Thread {
|
|||||||
try {
|
try {
|
||||||
Log.d(TAG, "Listening for new Bluetooth request from client.");
|
Log.d(TAG, "Listening for new Bluetooth request from client.");
|
||||||
Request incomingRequest = Request.listenForRequest(connection);
|
Request incomingRequest = Request.listenForRequest(connection);
|
||||||
handleRequest(incomingRequest);
|
handleRequest(incomingRequest).send(connection);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.e(TAG, "Error receiving incoming connection over bluetooth - " + e.getMessage());
|
Log.e(TAG, "Error receiving incoming connection over bluetooth - " + e.getMessage());
|
||||||
}
|
}
|
||||||
@ -103,10 +115,31 @@ public class BluetoothServer extends Thread {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleRequest(Request request) {
|
private Response handleRequest(Request request) throws IOException {
|
||||||
|
|
||||||
Log.d(TAG, "Received Bluetooth request from client, will process it now.");
|
Log.d(TAG, "Received Bluetooth request from client, will process it now.");
|
||||||
|
|
||||||
|
try {
|
||||||
|
HttpDownloader downloader = new HttpDownloader("http://127.0.0.1/" + request.getPath(), context);
|
||||||
|
|
||||||
|
Response.ResponseBuilder builder;
|
||||||
|
|
||||||
|
if (request.getMethod().equals(Request.Methods.HEAD)) {
|
||||||
|
builder = new Response.ResponseBuilder();
|
||||||
|
} else {
|
||||||
|
builder = new Response.ResponseBuilder(downloader.getInputStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: At this stage, will need to download the file to get this info.
|
||||||
|
// However, should be able to make totalDownloadSize and getCacheTag work without downloading.
|
||||||
|
return builder
|
||||||
|
.setStatusCode(200)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOException("Error getting file " + request.getPath() + " from local repo proxy - " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
package org.fdroid.fdroid.net.bluetooth.httpish;
|
package org.fdroid.fdroid.net.bluetooth.httpish;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
import org.fdroid.fdroid.net.bluetooth.BluetoothConnection;
|
import org.fdroid.fdroid.net.bluetooth.BluetoothConnection;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.BufferedWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -10,6 +15,8 @@ import java.util.Map;
|
|||||||
public class Request {
|
public class Request {
|
||||||
|
|
||||||
|
|
||||||
|
private static final String TAG = "org.fdroid.fdroid.net.bluetooth.httpish.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";
|
||||||
@ -27,6 +34,9 @@ public class Request {
|
|||||||
this.method = method;
|
this.method = method;
|
||||||
this.path = path;
|
this.path = path;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
|
|
||||||
|
output = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));
|
||||||
|
input = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Request createHEAD(String path, BluetoothConnection connection)
|
public static Request createHEAD(String path, BluetoothConnection connection)
|
||||||
@ -40,8 +50,7 @@ public class Request {
|
|||||||
|
|
||||||
public Response send() throws IOException {
|
public Response send() throws IOException {
|
||||||
|
|
||||||
output = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream()));
|
Log.d(TAG, "Sending request to server (" + path + ")");
|
||||||
input = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
|
||||||
|
|
||||||
output.write(method);
|
output.write(method);
|
||||||
output.write(' ');
|
output.write(' ');
|
||||||
@ -49,12 +58,23 @@ public class Request {
|
|||||||
|
|
||||||
output.write("\n\n");
|
output.write("\n\n");
|
||||||
|
|
||||||
|
output.flush();
|
||||||
|
|
||||||
|
Log.d(TAG, "Finished sending request, now attempting to read response status code...");
|
||||||
|
|
||||||
int responseCode = readResponseCode();
|
int responseCode = readResponseCode();
|
||||||
|
|
||||||
|
Log.d(TAG, "Read response code " + responseCode + " from server, now reading headers...");
|
||||||
|
|
||||||
Map<String, String> headers = readHeaders();
|
Map<String, String> headers = readHeaders();
|
||||||
|
|
||||||
|
Log.d(TAG, "Read " + headers.size() + " headers");
|
||||||
|
|
||||||
if (method.equals(Methods.HEAD)) {
|
if (method.equals(Methods.HEAD)) {
|
||||||
|
Log.d(TAG, "Request was a " + Methods.HEAD + " request, not including anything other than headers and status...");
|
||||||
return new Response(responseCode, headers);
|
return new Response(responseCode, headers);
|
||||||
} else {
|
} else {
|
||||||
|
Log.d(TAG, "Request was a " + Methods.GET + " request, so including content stream in response...");
|
||||||
return new Response(responseCode, headers, connection.getInputStream());
|
return new Response(responseCode, headers, connection.getInputStream());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,9 +129,9 @@ public class Request {
|
|||||||
|
|
||||||
// TODO: Error handling
|
// TODO: Error handling
|
||||||
int firstSpace = line.indexOf(' ');
|
int firstSpace = line.indexOf(' ');
|
||||||
int secondSpace = line.indexOf(' ', firstSpace);
|
int secondSpace = line.indexOf(' ', firstSpace + 1);
|
||||||
|
|
||||||
String status = line.substring(firstSpace, secondSpace);
|
String status = line.substring(firstSpace + 1, secondSpace);
|
||||||
return Integer.parseInt(status);
|
return Integer.parseInt(status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,6 +155,12 @@ public class Request {
|
|||||||
return headers;
|
return headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMethod() {
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,22 @@
|
|||||||
package org.fdroid.fdroid.net.bluetooth.httpish;
|
package org.fdroid.fdroid.net.bluetooth.httpish;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
import org.fdroid.fdroid.Utils;
|
||||||
|
import org.fdroid.fdroid.net.bluetooth.BluetoothConnection;
|
||||||
import org.fdroid.fdroid.net.bluetooth.FileDetails;
|
import org.fdroid.fdroid.net.bluetooth.FileDetails;
|
||||||
import org.fdroid.fdroid.net.bluetooth.httpish.headers.Header;
|
import org.fdroid.fdroid.net.bluetooth.httpish.headers.Header;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStreamWriter;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class Response {
|
public class Response {
|
||||||
|
|
||||||
|
private static final String TAG = "org.fdroid.fdroid.net.bluetooth.httpish.Response";
|
||||||
|
|
||||||
private int statusCode;
|
private int statusCode;
|
||||||
private Map<String, String> headers;
|
private Map<String, String> headers;
|
||||||
private final InputStream contentStream;
|
private final InputStream contentStream;
|
||||||
@ -44,13 +53,80 @@ public class Response {
|
|||||||
return details;
|
return details;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* After parsing a response,
|
|
||||||
*/
|
|
||||||
public InputStream toContentStream() throws UnsupportedOperationException {
|
public InputStream toContentStream() throws UnsupportedOperationException {
|
||||||
if (contentStream == null) {
|
if (contentStream == null) {
|
||||||
throw new UnsupportedOperationException("This kind of response doesn't have a content stream. Did you perform a HEAD request instead of a GET request?");
|
throw new UnsupportedOperationException("This kind of response doesn't have a content stream. Did you perform a HEAD request instead of a GET request?");
|
||||||
}
|
}
|
||||||
return contentStream;
|
return contentStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void send(BluetoothConnection connection) throws IOException {
|
||||||
|
|
||||||
|
Log.d(TAG, "Sending Bluetooth HTTP-ish response...");
|
||||||
|
|
||||||
|
Writer output = new OutputStreamWriter(connection.getOutputStream());
|
||||||
|
output.write("HTTP(ish)/0.1 200 OK\n");
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> entry : headers.entrySet()) {
|
||||||
|
output.write(entry.getKey());
|
||||||
|
output.write(": ");
|
||||||
|
output.write(entry.getValue());
|
||||||
|
output.write("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
output.write("\n");
|
||||||
|
output.flush();
|
||||||
|
|
||||||
|
if (contentStream != null) {
|
||||||
|
Utils.copy(contentStream, connection.getOutputStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
output.flush();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ResponseBuilder {
|
||||||
|
|
||||||
|
private InputStream contentStream;
|
||||||
|
private int statusCode = 200;
|
||||||
|
private int fileSize = -1;
|
||||||
|
private String etag = null;
|
||||||
|
|
||||||
|
public ResponseBuilder() {}
|
||||||
|
|
||||||
|
public ResponseBuilder(InputStream contentStream) {
|
||||||
|
this.contentStream = contentStream;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseBuilder setStatusCode(int statusCode) {
|
||||||
|
this.statusCode = statusCode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseBuilder setFileSize(int fileSize) {
|
||||||
|
this.fileSize = fileSize;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseBuilder setETag(String etag) {
|
||||||
|
this.etag = etag;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Response build() {
|
||||||
|
|
||||||
|
Map<String, String> headers = new HashMap<>(3);
|
||||||
|
|
||||||
|
if (fileSize > 0) {
|
||||||
|
headers.put("Content-Length", Integer.toString(fileSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (etag != null) {
|
||||||
|
headers.put( "ETag", etag);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response(statusCode, headers, contentStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,26 +1,168 @@
|
|||||||
package org.fdroid.fdroid.views.swap;
|
package org.fdroid.fdroid.views.swap;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothAdapter;
|
||||||
import android.bluetooth.BluetoothDevice;
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.widget.ContentLoadingProgressBar;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.ContextThemeWrapper;
|
import android.view.ContextThemeWrapper;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.MotionEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.data.InstalledAppProvider;
|
import org.fdroid.fdroid.net.bluetooth.BluetoothClient;
|
||||||
|
import org.fdroid.fdroid.net.bluetooth.BluetoothConnection;
|
||||||
|
import org.fdroid.fdroid.net.bluetooth.httpish.Request;
|
||||||
|
import org.fdroid.fdroid.net.bluetooth.httpish.Response;
|
||||||
import org.fdroid.fdroid.views.fragments.ThemeableListFragment;
|
import org.fdroid.fdroid.views.fragments.ThemeableListFragment;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class BluetoothDeviceListFragment extends ThemeableListFragment {
|
public class BluetoothDeviceListFragment extends ThemeableListFragment {
|
||||||
|
|
||||||
|
private static final String TAG = "org.fdroid.fdroid.views.swap.BluetoothDeviceListFragment";
|
||||||
|
|
||||||
private Adapter adapter = null;
|
private Adapter adapter = null;
|
||||||
|
|
||||||
|
@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 = new Adapter(
|
||||||
|
new ContextThemeWrapper(getActivity(), R.style.SwapTheme_BluetoothDeviceList_ListItem),
|
||||||
|
R.layout.select_local_apps_list_item
|
||||||
|
);
|
||||||
|
|
||||||
|
populateDeviceList();
|
||||||
|
|
||||||
|
setListAdapter(adapter);
|
||||||
|
setListShown(false); // start out with a progress indicator
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
View view = super.onCreateView(inflater, container, savedInstanceState);
|
||||||
|
View headerView = getHeaderView();
|
||||||
|
if (headerView == null) {
|
||||||
|
Log.e(TAG, "Could not find header view, although we expected one to exist.");
|
||||||
|
} else {
|
||||||
|
headerView.setOnTouchListener(new View.OnTouchListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
|
initiateBluetoothScan();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initiateBluetoothScan()
|
||||||
|
{
|
||||||
|
BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter();
|
||||||
|
BroadcastReceiver deviceFoundReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
if (BluetoothDevice.ACTION_FOUND.equals(intent.getAction())) {
|
||||||
|
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||||
|
boolean exists = false;
|
||||||
|
for (int i = 0; i < adapter.getCount(); i ++) {
|
||||||
|
if (adapter.getItem(i).getAddress().equals(device.getAddress())) {
|
||||||
|
exists = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!exists) {
|
||||||
|
adapter.add(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
((ContentLoadingProgressBar)getView().findViewById(R.id.loading_indicator)).show();
|
||||||
|
getActivity().registerReceiver(deviceFoundReceiver, new IntentFilter(BluetoothDevice.ACTION_FOUND));
|
||||||
|
|
||||||
|
if (!bluetooth.startDiscovery()) {
|
||||||
|
// TODO: Discovery did not start for some reason :(
|
||||||
|
Log.e(TAG, "Could not start bluetooth discovery, but am not sure why :(");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void populateDeviceList()
|
||||||
|
{
|
||||||
|
for (BluetoothDevice device : BluetoothAdapter.getDefaultAdapter().getBondedDevices()) {
|
||||||
|
adapter.add(device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onListItemClick(ListView l, View v, int position, long id) {
|
||||||
|
|
||||||
|
// "position" includes the header view, so ignore that.
|
||||||
|
if (position == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
BluetoothDevice device = adapter.getItem(position - 1);
|
||||||
|
|
||||||
|
// TODO: I think that I can connect regardless of the bond state.
|
||||||
|
// It sounds like when I attempt to connect to a non-bonded peer, then
|
||||||
|
// Android initiates the pairing dialog on our behalf.
|
||||||
|
|
||||||
|
BluetoothClient client = new BluetoothClient(device);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Testing bluetooth connection (opening connection first).");
|
||||||
|
BluetoothConnection connection = client.openConnection();
|
||||||
|
Log.d(TAG, "Creating HEAD request for resource at \"/\"...");
|
||||||
|
Request head = Request.createHEAD("/", connection);
|
||||||
|
Log.d(TAG, "Sending request...");
|
||||||
|
Response response = head.send();
|
||||||
|
Log.d(TAG, "Response from bluetooth: " + response.getStatusCode());
|
||||||
|
} catch (IOException e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/*if (device.getBondState() == BluetoothDevice.BOND_NONE) {
|
||||||
|
// attempt to bond
|
||||||
|
|
||||||
|
} else if (device.getBondState() == BluetoothDevice.BOND_BONDING) {
|
||||||
|
// wait for bonding to finish
|
||||||
|
|
||||||
|
} else if (device.getBondState() == BluetoothDevice.BOND_BONDED) {
|
||||||
|
// connect
|
||||||
|
BluetoothClient client = new BluetoothClient(device);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getThemeStyle() {
|
||||||
|
return R.style.SwapTheme_BluetoothDeviceList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getHeaderLayout() {
|
||||||
|
return R.layout.swap_bluetooth_header;
|
||||||
|
}
|
||||||
|
|
||||||
private class Adapter extends ArrayAdapter<BluetoothDevice> {
|
private class Adapter extends ArrayAdapter<BluetoothDevice> {
|
||||||
|
|
||||||
public Adapter(Context context, int resource) {
|
public Adapter(Context context, int resource) {
|
||||||
@ -51,61 +193,37 @@ public class BluetoothDeviceListFragment extends ThemeableListFragment {
|
|||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
View view;
|
View view;
|
||||||
if (convertView == null) {
|
if (convertView == null) {
|
||||||
view = getActivity().getLayoutInflater().inflate(android.R.layout.simple_list_item_2, parent);
|
view = getActivity().getLayoutInflater().inflate(R.layout.simple_list_item_3, null);
|
||||||
} else {
|
} else {
|
||||||
view = convertView;
|
view = convertView;
|
||||||
}
|
}
|
||||||
|
|
||||||
BluetoothDevice device = getItem(position);
|
BluetoothDevice device = getItem(position);
|
||||||
TextView nameView = (TextView)view.findViewById(android.R.id.text1);
|
TextView nameView = (TextView)view.findViewById(android.R.id.text1);
|
||||||
TextView descriptionView = (TextView)view.findViewById(android.R.id.text2);
|
TextView addressView = (TextView)view.findViewById(android.R.id.text2);
|
||||||
|
TextView descriptionView = (TextView)view.findViewById(R.id.text3);
|
||||||
|
|
||||||
nameView.setText(device.getName());
|
nameView.setText(device.getName());
|
||||||
descriptionView.setText(device.getAddress());
|
addressView.setText(device.getAddress());
|
||||||
|
descriptionView.setText(bondStateToLabel(device.getBondState()));
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private String bondStateToLabel(int deviceBondState)
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
{
|
||||||
super.onCreate(savedInstanceState);
|
if (deviceBondState == BluetoothDevice.BOND_BONDED) {
|
||||||
setHasOptionsMenu(false);
|
// TODO: Is the term "Bonded device" common parlance among phone users?
|
||||||
}
|
// It sounds a bit technical to me, maybe something more lay like "Previously connected".
|
||||||
|
// Although it is technically not as accurate, it would make sense to more people...
|
||||||
@Override
|
return "Bonded";
|
||||||
public void onActivityCreated(Bundle savedInstanceState) {
|
} else if (deviceBondState == BluetoothDevice.BOND_BONDING) {
|
||||||
super.onActivityCreated(savedInstanceState);
|
return "Currently bonding...";
|
||||||
|
} else {
|
||||||
setEmptyText("No bluetooth devices found. Is the other device \"discoverable\"?");
|
// TODO: Might be a little bit harsh, makes it sound more malicious than it should.
|
||||||
|
return "Unknown device";
|
||||||
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