related to #404 improvement to patterns to support close and discoverable

You must, must, must close the BT sockets else you run out of them.
This code tries to handle all the places where BT sockets may not get closed.
It also tries to tweak user experience/UI integration pieces in a few areas,
and handle some NPEs that can occur when BT fails.
This commit is contained in:
n8fr8 2015-09-10 14:44:59 -04:00
parent 0a96d17dd1
commit b3f8ac0a5b
14 changed files with 77 additions and 14 deletions

View File

@ -98,7 +98,9 @@ public class RepoUpdater {
} catch (IOException e) { } catch (IOException e) {
if (downloader != null && downloader.getFile() != null) { if (downloader != null && downloader.getFile() != null) {
downloader.getFile().delete(); downloader.getFile().delete();
downloader.close();
} }
throw new UpdateException(repo, "Error getting index file from " + repo.address, e); throw new UpdateException(repo, "Error getting index file from " + repo.address, e);
} }
return downloader; return downloader;
@ -121,6 +123,8 @@ public class RepoUpdater {
// successful download, then we will have a file ready to use: // successful download, then we will have a file ready to use:
processDownloadedFile(downloader.getFile(), downloader.getCacheTag()); processDownloadedFile(downloader.getFile(), downloader.getCacheTag());
} }
downloader.close();
} }
protected void processDownloadedFile(File downloadedFile, String cacheTag) throws UpdateException { protected void processDownloadedFile(File downloadedFile, String cacheTag) throws UpdateException {

View File

@ -456,7 +456,7 @@ public class SwapService extends Service {
} }
public boolean isBluetoothDiscoverable() { public boolean isBluetoothDiscoverable() {
return bluetoothSwap.isConnected(); return bluetoothSwap.isDiscoverable();
} }
public boolean isBonjourDiscoverable() { public boolean isBonjourDiscoverable() {

View File

@ -15,6 +15,8 @@ public class BluetoothFinder extends PeerFinder<BluetoothPeer> {
private static final String TAG = "BluetoothFinder"; private static final String TAG = "BluetoothFinder";
public final static int DISCOVERABLE_TIMEOUT = 3600;
private final BluetoothAdapter adapter; private final BluetoothAdapter adapter;
public BluetoothFinder(Context context) { public BluetoothFinder(Context context) {

View File

@ -23,6 +23,7 @@ public class BluetoothSwap extends SwapType {
@NonNull @NonNull
private final BluetoothAdapter adapter; private final BluetoothAdapter adapter;
private BroadcastReceiver receiver; private BroadcastReceiver receiver;
private boolean isDiscoverable = false;
@Nullable @Nullable
private BluetoothServer server; private BluetoothServer server;
@ -48,6 +49,11 @@ public class BluetoothSwap extends SwapType {
} }
@Override
public boolean isDiscoverable () {
return isDiscoverable;
}
@Override @Override
public boolean isConnected() { public boolean isConnected() {
return server != null && server.isRunning() && super.isConnected(); return server != null && server.isRunning() && super.isConnected();
@ -68,6 +74,7 @@ public class BluetoothSwap extends SwapType {
break; break;
case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE: case BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE:
isDiscoverable = true;
if (server != null && server.isRunning()) { if (server != null && server.isRunning()) {
setConnected(true); setConnected(true);
} }
@ -163,5 +170,6 @@ public class BluetoothSwap extends SwapType {
protected String getBroadcastAction() { protected String getBroadcastAction() {
return null; return null;
} }
} }
} }

View File

@ -33,6 +33,11 @@ public abstract class SwapType {
abstract protected String getBroadcastAction(); abstract protected String getBroadcastAction();
public boolean isDiscoverable ()
{
return isConnected();
}
protected final void setConnected(boolean connected) { protected final void setConnected(boolean connected) {
if (connected) { if (connected) {
isConnected = true; isConnected = true;

View File

@ -48,6 +48,7 @@ public class BluetoothDownloader extends Downloader {
// to us). // to us).
BoundedInputStream stream = new BoundedInputStream(response.toContentStream(), fileDetails.getFileSize()); BoundedInputStream stream = new BoundedInputStream(response.toContentStream(), fileDetails.getFileSize());
stream.setPropagateClose(false); stream.setPropagateClose(false);
return stream; return stream;
} }
@ -95,4 +96,11 @@ public class BluetoothDownloader extends Downloader {
); );
} }
@Override
public void close ()
{
if (connection != null)
connection.closeQuietly();
}
} }

View File

@ -40,6 +40,7 @@ public abstract class Downloader {
protected int totalBytes = 0; protected int totalBytes = 0;
public abstract InputStream getInputStream() throws IOException; public abstract InputStream getInputStream() throws IOException;
public abstract void close();
Downloader(Context context, URL url, File destFile) Downloader(Context context, URL url, File destFile)
throws FileNotFoundException, MalformedURLException { throws FileNotFoundException, MalformedURLException {

View File

@ -167,4 +167,8 @@ public class HttpDownloader extends Downloader {
return statusCode; return statusCode;
} }
public void close ()
{
connection.disconnect();
}
} }

View File

@ -3,7 +3,10 @@ package org.fdroid.fdroid.net;
import android.content.Context; import android.content.Context;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader; import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
import com.nostra13.universalimageloader.utils.IoUtils;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -33,11 +36,27 @@ public class IconDownloader extends BaseImageDownloader {
if (imageUri.toLowerCase().startsWith("bluetooth")) if (imageUri.toLowerCase().startsWith("bluetooth"))
{ {
Downloader downloader = DownloaderFactory.create(context, imageUri); Downloader downloader = DownloaderFactory.create(context, imageUri);
return downloader.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
InputStream is = downloader.getInputStream();
int b = -1;
while ((b = is.read())!=-1)
baos.write(b);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
downloader.close();
return bais;
} }
return super.getStream(imageUri, extra); return super.getStream(imageUri, extra);
} }
} }

View File

@ -28,12 +28,20 @@ public class BluetoothClient {
BluetoothSocket socket = null; BluetoothSocket socket = null;
BluetoothConnection connection = null;
try { try {
socket = device.createInsecureRfcommSocketToServiceRecord(BluetoothConstants.fdroidUuid()); socket = device.createInsecureRfcommSocketToServiceRecord(BluetoothConstants.fdroidUuid());
BluetoothConnection connection = new BluetoothConnection(socket); connection = new BluetoothConnection(socket);
connection.open(); connection.open();
return connection; return connection;
} catch (IOException e1) { } catch (IOException e1) {
if (connection != null)
connection.closeQuietly();
throw e1;
/*
Log.e(TAG, "There was an error while establishing Bluetooth connection. Falling back to using reflection..."); Log.e(TAG, "There was an error while establishing Bluetooth connection. Falling back to using reflection...");
Class<?> clazz = socket.getRemoteDevice().getClass(); Class<?> clazz = socket.getRemoteDevice().getClass();
Class<?>[] paramTypes = new Class<?>[]{Integer.TYPE}; Class<?>[] paramTypes = new Class<?>[]{Integer.TYPE};
@ -43,6 +51,7 @@ public class BluetoothClient {
method = clazz.getMethod("createInsecureRfcommSocket", paramTypes); method = clazz.getMethod("createInsecureRfcommSocket", paramTypes);
Object[] params = new Object[]{1}; Object[] params = new Object[]{1};
BluetoothSocket sockFallback = (BluetoothSocket) method.invoke(socket.getRemoteDevice(), params); BluetoothSocket sockFallback = (BluetoothSocket) method.invoke(socket.getRemoteDevice(), params);
BluetoothConnection connection = new BluetoothConnection(sockFallback); BluetoothConnection connection = new BluetoothConnection(sockFallback);
connection.open(); connection.open();
return connection; return connection;
@ -52,7 +61,7 @@ public class BluetoothClient {
throw e1; throw e1;
} catch (InvocationTargetException e) { } catch (InvocationTargetException e) {
throw e1; throw e1;
} }*/
// Don't catch exceptions this time, let it bubble up as we did our best but don't // Don't catch exceptions this time, let it bubble up as we did our best but don't
// have anythign else to offer in terms of resolving the problem right now. // have anythign else to offer in terms of resolving the problem right now.

View File

@ -53,12 +53,6 @@ public class BluetoothConnection {
} }
public void close() throws IOException { public void close() throws IOException {
if (input == null || output == null) { closeQuietly();
throw new RuntimeException("Cannot close() a BluetoothConnection before calling open()" );
}
input.close();
output.close();
socket.close();
} }
} }

View File

@ -137,6 +137,8 @@ public class BluetoothServer extends Thread {
break; break;
} }
connection.closeQuietly();
} }
private Response handleRequest(Request request) throws IOException { private Response handleRequest(Request request) throws IOException {

View File

@ -283,7 +283,7 @@ public class SwapAppsView extends ListView implements
Apk apk = getApkToInstall(); Apk apk = getApkToInstall();
String broadcastUrl = intent.getStringExtra(Downloader.EXTRA_ADDRESS); String broadcastUrl = intent.getStringExtra(Downloader.EXTRA_ADDRESS);
if (apk.repoAddress != null && (!TextUtils.equals(Utils.getApkUrl(apk.repoAddress, apk), broadcastUrl))) { if (apk != null && apk.repoAddress != null && (!TextUtils.equals(Utils.getApkUrl(apk.repoAddress, apk), broadcastUrl))) {
return; return;
} }
@ -374,9 +374,15 @@ public class SwapAppsView extends ListView implements
private void resetView() { private void resetView() {
if (app == null)
return;
progressView.setVisibility(View.GONE); progressView.setVisibility(View.GONE);
progressView.setIndeterminate(true); progressView.setIndeterminate(true);
if (app.name != null)
nameView.setText(app.name); nameView.setText(app.name);
ImageLoader.getInstance().displayImage(app.iconUrl, iconView, displayImageOptions); ImageLoader.getInstance().displayImage(app.iconUrl, iconView, displayImageOptions);
btnInstall.setVisibility(View.GONE); btnInstall.setVisibility(View.GONE);

View File

@ -44,6 +44,7 @@ import org.fdroid.fdroid.data.NewRepoConfig;
import org.fdroid.fdroid.installer.Installer; import org.fdroid.fdroid.installer.Installer;
import org.fdroid.fdroid.localrepo.LocalRepoManager; import org.fdroid.fdroid.localrepo.LocalRepoManager;
import org.fdroid.fdroid.localrepo.SwapService; import org.fdroid.fdroid.localrepo.SwapService;
import org.fdroid.fdroid.localrepo.peers.BluetoothFinder;
import org.fdroid.fdroid.localrepo.peers.Peer; import org.fdroid.fdroid.localrepo.peers.Peer;
import org.fdroid.fdroid.net.ApkDownloader; import org.fdroid.fdroid.net.ApkDownloader;
@ -625,7 +626,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
Log.d(TAG, "Not currently in discoverable mode, so prompting user to enable."); Log.d(TAG, "Not currently in discoverable mode, so prompting user to enable.");
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 3600); // 3600 is new maximum! TODO: What about when this expires? What if user manually disables discovery? intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, BluetoothFinder.DISCOVERABLE_TIMEOUT); // 3600 is new maximum! TODO: What about when this expires? What if user manually disables discovery?
startActivityForResult(intent, REQUEST_BLUETOOTH_DISCOVERABLE); startActivityForResult(intent, REQUEST_BLUETOOTH_DISCOVERABLE);
} }