Merge branch 'WifiStateChangeService-fixes' into 'master'

WifiStateChangeService fixes

`WifiStateChangeService` had a number of issues, including being run often when there was no change, and being run twice at start-up.  Also, its _complete_ broadcast was being sent twice, in effect. That made for a lot of flaky behavior for the things that rely on that information, namely wifi swap.

See merge request !289
This commit is contained in:
Daniel Martí 2016-05-13 11:06:18 +00:00
commit f7299c6537
8 changed files with 81 additions and 79 deletions

View File

@ -20,6 +20,8 @@ package org.fdroid.fdroid;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.Application; import android.app.Application;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothManager;
@ -79,12 +81,12 @@ public class FDroidApp extends Application {
private static Locale locale; private static Locale locale;
// for the local repo on this device, all static since there is only one // for the local repo on this device, all static since there is only one
public static int port; public static volatile int port;
public static String ipAddressString; public static volatile String ipAddressString;
public static SubnetUtils.SubnetInfo subnetInfo; public static volatile SubnetUtils.SubnetInfo subnetInfo;
public static String ssid; public static volatile String ssid;
public static String bssid; public static volatile String bssid;
public static final Repo REPO = new Repo(); public static volatile Repo repo = new Repo();
// 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 SPONGYCASTLE_PROVIDER; private static final org.spongycastle.jce.provider.BouncyCastleProvider SPONGYCASTLE_PROVIDER;
@ -145,6 +147,7 @@ public class FDroidApp extends Application {
subnetInfo = new SubnetUtils("0.0.0.0/32").getInfo(); subnetInfo = new SubnetUtils("0.0.0.0/32").getInfo();
ssid = ""; ssid = "";
bssid = ""; bssid = "";
repo = new Repo();
} }
public void updateLanguage() { public void updateLanguage() {
@ -183,7 +186,16 @@ public class FDroidApp extends Application {
.build()); .build());
} }
updateLanguage(); updateLanguage();
ACRA.init(this); ACRA.init(this);
// if this is the ACRA process, do not run the rest of onCreate()
int pid = android.os.Process.myPid();
ActivityManager manager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
for (RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) {
if (processInfo.pid == pid && "org.fdroid.fdroid:acra".equals(processInfo.processName)) {
return;
}
}
PRNGFixes.apply(); PRNGFixes.apply();

View File

@ -7,6 +7,15 @@ import java.net.URL;
* updates, APKs, etc). This also keeps this class pure Java so that classes * updates, APKs, etc). This also keeps this class pure Java so that classes
* that use {@code ProgressListener} can be tested on the JVM, without requiring * that use {@code ProgressListener} can be tested on the JVM, without requiring
* an Android device or emulator. * an Android device or emulator.
* <p/>
* The full URL of a download is used as the unique identifier throughout
* F-Droid. I can take a few forms:
* <ul>
* <li>{@link URL} instances
* <li>{@link android.net.Uri} instances
* <li>{@code String} instances, i.e. {@link URL#toString()}
* <li>{@code int}s, i.e. {@link String#hashCode()}
* </ul>
*/ */
public interface ProgressListener { public interface ProgressListener {

View File

@ -31,7 +31,6 @@ import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import org.fdroid.fdroid.UpdateService; import org.fdroid.fdroid.UpdateService;
import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.NewRepoConfig;
import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.localrepo.peers.Peer; import org.fdroid.fdroid.localrepo.peers.Peer;
@ -208,16 +207,12 @@ public class SwapService extends Service {
askServerToSwapWithUs(repo.address); askServerToSwapWithUs(repo.address);
} }
public void askServerToSwapWithUs(final NewRepoConfig config) {
askServerToSwapWithUs(config.getRepoUriString());
}
private void askServerToSwapWithUs(final String address) { private void askServerToSwapWithUs(final String address) {
new AsyncTask<Void, Void, Void>() { new AsyncTask<Void, Void, Void>() {
@Override @Override
protected Void doInBackground(Void... args) { protected Void doInBackground(Void... args) {
Uri repoUri = Uri.parse(address); Uri repoUri = Uri.parse(address);
String swapBackUri = Utils.getLocalRepoUri(FDroidApp.REPO).toString(); String swapBackUri = Utils.getLocalRepoUri(FDroidApp.repo).toString();
AndroidHttpClient client = AndroidHttpClient.newInstance("F-Droid", SwapService.this); AndroidHttpClient client = AndroidHttpClient.newInstance("F-Droid", SwapService.this);
HttpPost request = new HttpPost("/request-swap"); HttpPost request = new HttpPost("/request-swap");

View File

@ -130,7 +130,7 @@ class BonjourFinder extends PeerFinder implements ServiceListener {
final String type = serviceInfo.getPropertyString("type"); final String type = serviceInfo.getPropertyString("type");
final String fingerprint = serviceInfo.getPropertyString("fingerprint"); final String fingerprint = serviceInfo.getPropertyString("fingerprint");
final boolean isFDroid = type != null && type.startsWith("fdroidrepo"); final boolean isFDroid = type != null && type.startsWith("fdroidrepo");
final boolean isSelf = FDroidApp.REPO != null && fingerprint != null && fingerprint.equalsIgnoreCase(FDroidApp.REPO.fingerprint); final boolean isSelf = FDroidApp.repo != null && fingerprint != null && fingerprint.equalsIgnoreCase(FDroidApp.repo.fingerprint);
if (isFDroid && !isSelf) { if (isFDroid && !isSelf) {
Utils.debugLog(TAG, "Found F-Droid swap Bonjour service:\n" + serviceInfo); Utils.debugLog(TAG, "Found F-Droid swap Bonjour service:\n" + serviceInfo);
subscriber.onNext(new BonjourPeer(serviceInfo)); subscriber.onNext(new BonjourPeer(serviceInfo));

View File

@ -55,7 +55,7 @@ public class BonjourBroadcast extends SwapType {
HashMap<String, String> values = new HashMap<>(); HashMap<String, String> values = new HashMap<>();
values.put("path", "/fdroid/repo"); values.put("path", "/fdroid/repo");
values.put("name", repoName); values.put("name", repoName);
values.put("fingerprint", FDroidApp.REPO.fingerprint); values.put("fingerprint", FDroidApp.repo.fingerprint);
String type; String type;
if (Preferences.get().isLocalRepoHttpsEnabled()) { if (Preferences.get().isLocalRepoHttpsEnabled()) {
values.put("type", "fdroidrepos"); values.put("type", "fdroidrepos");

View File

@ -1,18 +1,14 @@
package org.fdroid.fdroid.net; package org.fdroid.fdroid.net;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Service; import android.app.IntentService;
import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection;
import android.net.DhcpInfo; import android.net.DhcpInfo;
import android.net.NetworkInfo; import android.net.NetworkInfo;
import android.net.wifi.WifiInfo; import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
@ -21,9 +17,9 @@ import org.apache.commons.net.util.SubnetUtils;
import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.localrepo.LocalRepoKeyStore; import org.fdroid.fdroid.localrepo.LocalRepoKeyStore;
import org.fdroid.fdroid.localrepo.LocalRepoManager; import org.fdroid.fdroid.localrepo.LocalRepoManager;
import org.fdroid.fdroid.localrepo.SwapService;
import java.net.Inet6Address; import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
@ -34,54 +30,63 @@ import java.security.cert.Certificate;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.Locale; import java.util.Locale;
public class WifiStateChangeService extends Service { /**
* Handle state changes to the device's wifi, storing the required bits.
* The {@link Intent} that starts it either has no extras included,
* which is how it can be triggered by code, or it came in from the system
* via {@link org.fdroid.fdroid.receiver.WifiStateChangeReceiver}, in
* which case an instance of {@link NetworkInfo} is included.
*/
public class WifiStateChangeService extends IntentService {
private static final String TAG = "WifiStateChangeService"; private static final String TAG = "WifiStateChangeService";
public static final String BROADCAST = "org.fdroid.fdroid.action.WIFI_CHANGE"; public static final String BROADCAST = "org.fdroid.fdroid.action.WIFI_CHANGE";
private WifiManager wifiManager; private WifiManager wifiManager;
private static WaitForWifiAsyncTask asyncTask; private static WifiInfoThread wifiInfoThread;
private int wifiState;
public WifiStateChangeService() {
super("WifiStateChangeService");
}
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { protected void onHandleIntent(Intent intent) {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST);
Utils.debugLog(TAG, "WiFi change service started, clearing info about wifi state until we have figured it out again."); Utils.debugLog(TAG, "WiFi change service started, clearing info about wifi state until we have figured it out again.");
FDroidApp.initWifiSettings(); FDroidApp.initWifiSettings();
NetworkInfo ni = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); NetworkInfo ni = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
wifiManager = (WifiManager) getSystemService(WIFI_SERVICE); wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
wifiState = wifiManager.getWifiState(); int wifiState = wifiManager.getWifiState();
if (ni == null || ni.isConnected()) { if (ni == null || ni.isConnected()) {
/* started on app start or from WifiStateChangeReceiver,
NetworkInfo is only passed via WifiStateChangeReceiver */
Utils.debugLog(TAG, "ni == " + ni + " wifiState == " + printWifiState(wifiState)); Utils.debugLog(TAG, "ni == " + ni + " wifiState == " + printWifiState(wifiState));
if (wifiState == WifiManager.WIFI_STATE_ENABLED if (wifiState == WifiManager.WIFI_STATE_ENABLED
|| wifiState == WifiManager.WIFI_STATE_DISABLING // might be switching to hotspot || wifiState == WifiManager.WIFI_STATE_DISABLING // might be switching to hotspot
|| wifiState == WifiManager.WIFI_STATE_DISABLED // might be hotspot || wifiState == WifiManager.WIFI_STATE_DISABLED // might be hotspot
|| wifiState == WifiManager.WIFI_STATE_UNKNOWN) { // might be hotspot || wifiState == WifiManager.WIFI_STATE_UNKNOWN) { // might be hotspot
if (asyncTask != null) { if (wifiInfoThread != null) {
asyncTask.cancel(true); wifiInfoThread.interrupt();
} }
asyncTask = new WaitForWifiAsyncTask(); wifiInfoThread = new WifiInfoThread();
asyncTask.execute(); wifiInfoThread.start();
} }
} }
return START_NOT_STICKY;
} }
public class WaitForWifiAsyncTask extends AsyncTask<Void, Void, Void> { public class WifiInfoThread extends Thread {
private static final String TAG = "WaitForWifiAsyncTask"; private static final String TAG = "WifiInfoThread";
@Override @Override
protected Void doInBackground(Void... params) { public void run() {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST);
try { try {
Utils.debugLog(TAG, "Checking wifi state (in background thread)."); Utils.debugLog(TAG, "Checking wifi state (in background thread).");
WifiInfo wifiInfo = null; WifiInfo wifiInfo = null;
wifiState = wifiManager.getWifiState(); int wifiState = wifiManager.getWifiState();
while (FDroidApp.ipAddressString == null) { while (FDroidApp.ipAddressString == null) {
if (isCancelled()) { // can be canceled by a change via WifiStateChangeReceiver if (isInterrupted()) { // can be canceled by a change via WifiStateChangeReceiver
return null; return;
} }
if (wifiState == WifiManager.WIFI_STATE_ENABLED) { if (wifiState == WifiManager.WIFI_STATE_ENABLED) {
wifiInfo = wifiManager.getConnectionInfo(); wifiInfo = wifiManager.getConnectionInfo();
@ -98,7 +103,7 @@ public class WifiStateChangeService extends Service {
// try once to see if its a hotspot // try once to see if its a hotspot
setIpInfoFromNetworkInterface(); setIpInfoFromNetworkInterface();
if (FDroidApp.ipAddressString == null) { if (FDroidApp.ipAddressString == null) {
return null; return;
} }
} else { // a hotspot can be active during WIFI_STATE_UNKNOWN } else { // a hotspot can be active during WIFI_STATE_UNKNOWN
setIpInfoFromNetworkInterface(); setIpInfoFromNetworkInterface();
@ -109,8 +114,8 @@ public class WifiStateChangeService extends Service {
Utils.debugLog(TAG, "waiting for an IP address..."); Utils.debugLog(TAG, "waiting for an IP address...");
} }
} }
if (isCancelled()) { // can be canceled by a change via WifiStateChangeReceiver if (isInterrupted()) { // can be canceled by a change via WifiStateChangeReceiver
return null; return;
} }
if (wifiInfo != null) { if (wifiInfo != null) {
@ -125,33 +130,35 @@ public class WifiStateChangeService extends Service {
} }
} }
// TODO: Can this be moved to the swap service instead?
String scheme; String scheme;
if (Preferences.get().isLocalRepoHttpsEnabled()) { if (Preferences.get().isLocalRepoHttpsEnabled()) {
scheme = "https"; scheme = "https";
} else { } else {
scheme = "http"; scheme = "http";
} }
FDroidApp.REPO.name = Preferences.get().getLocalRepoName(); Repo repo = new Repo();
FDroidApp.REPO.address = String.format(Locale.ENGLISH, "%s://%s:%d/fdroid/repo", repo.name = Preferences.get().getLocalRepoName();
repo.address = String.format(Locale.ENGLISH, "%s://%s:%d/fdroid/repo",
scheme, FDroidApp.ipAddressString, FDroidApp.port); scheme, FDroidApp.ipAddressString, FDroidApp.port);
if (isCancelled()) { // can be canceled by a change via WifiStateChangeReceiver if (isInterrupted()) { // can be canceled by a change via WifiStateChangeReceiver
return null; return;
} }
Context context = WifiStateChangeService.this.getApplicationContext(); Context context = WifiStateChangeService.this.getApplicationContext();
LocalRepoManager lrm = LocalRepoManager.get(context); LocalRepoManager lrm = LocalRepoManager.get(context);
lrm.writeIndexPage(Utils.getSharingUri(FDroidApp.REPO).toString()); lrm.writeIndexPage(Utils.getSharingUri(FDroidApp.repo).toString());
if (isCancelled()) { // can be canceled by a change via WifiStateChangeReceiver if (isInterrupted()) { // can be canceled by a change via WifiStateChangeReceiver
return null; return;
} }
// the fingerprint for the local repo's signing key // the fingerprint for the local repo's signing key
LocalRepoKeyStore localRepoKeyStore = LocalRepoKeyStore.get(context); LocalRepoKeyStore localRepoKeyStore = LocalRepoKeyStore.get(context);
Certificate localCert = localRepoKeyStore.getCertificate(); Certificate localCert = localRepoKeyStore.getCertificate();
FDroidApp.REPO.fingerprint = Utils.calcFingerprint(localCert); repo.fingerprint = Utils.calcFingerprint(localCert);
FDroidApp.repo = repo;
/* /*
* Once the IP address is known we need to generate a self * Once the IP address is known we need to generate a self
@ -164,36 +171,15 @@ public class WifiStateChangeService extends Service {
localRepoKeyStore.setupHTTPSCertificate(); localRepoKeyStore.setupHTTPSCertificate();
} }
} catch (LocalRepoKeyStore.InitException | InterruptedException e) { } catch (LocalRepoKeyStore.InitException e) {
Log.e(TAG, "Unable to configure a fingerprint or HTTPS for the local repo", e); Log.e(TAG, "Unable to configure a fingerprint or HTTPS for the local repo", e);
} catch (InterruptedException e) {
Utils.debugLog(TAG, "interrupted");
return;
} }
return null;
}
@Override
protected void onPostExecute(Void result) {
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();
Intent swapService = new Intent(WifiStateChangeService.this, SwapService.class);
getApplicationContext().bindService(swapService, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
((SwapService.Binder) service).getService().stopWifiIfEnabled(true);
getApplicationContext().unbindService(this);
} }
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, BIND_AUTO_CREATE);
}
}
@Override
public IBinder onBind(Intent intent) {
return null;
} }
@TargetApi(9) @TargetApi(9)

View File

@ -504,7 +504,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
// Even if they opted to skip the message which says "Touch devices to swap", // Even if they opted to skip the message which says "Touch devices to swap",
// we still want to actually enable the feature, so that they could touch // we still want to actually enable the feature, so that they could touch
// during the wifi qr code being shown too. // during the wifi qr code being shown too.
boolean nfcMessageReady = NfcHelper.setPushMessage(this, Utils.getSharingUri(FDroidApp.REPO)); boolean nfcMessageReady = NfcHelper.setPushMessage(this, Utils.getSharingUri(FDroidApp.repo));
if (Preferences.get().showNfcDuringSwap() && nfcMessageReady) { if (Preferences.get().showNfcDuringSwap() && nfcMessageReady) {
inflateInnerView(R.layout.swap_nfc); inflateInnerView(R.layout.swap_nfc);
@ -669,7 +669,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
PrepareSwapRepo(@NonNull Set<String> apps) { PrepareSwapRepo(@NonNull Set<String> apps) {
context = SwapWorkflowActivity.this; context = SwapWorkflowActivity.this;
selectedApps = apps; selectedApps = apps;
sharingUri = Utils.getSharingUri(FDroidApp.REPO); sharingUri = Utils.getSharingUri(FDroidApp.repo);
} }
private void broadcast(int type) { private void broadcast(int type) {

View File

@ -121,7 +121,7 @@ public class WifiQrView extends ScrollView implements SwapWorkflowActivity.Inner
private void setUIFromWifi() { private void setUIFromWifi() {
if (TextUtils.isEmpty(FDroidApp.REPO.address)) { if (TextUtils.isEmpty(FDroidApp.repo.address)) {
return; return;
} }
@ -139,7 +139,7 @@ public class WifiQrView extends ScrollView implements SwapWorkflowActivity.Inner
* wifi AP to join. Lots of QR Scanners are buggy and do not respect * wifi AP to join. Lots of QR Scanners are buggy and do not respect
* custom URI schemes, so we have to use http:// or https:// :-( * custom URI schemes, so we have to use http:// or https:// :-(
*/ */
Uri sharingUri = Utils.getSharingUri(FDroidApp.REPO); Uri sharingUri = Utils.getSharingUri(FDroidApp.repo);
String qrUriString = (scheme + sharingUri.getHost()).toUpperCase(Locale.ENGLISH); String qrUriString = (scheme + sharingUri.getHost()).toUpperCase(Locale.ENGLISH);
if (sharingUri.getPort() != 80) { if (sharingUri.getPort() != 80) {
qrUriString += ":" + sharingUri.getPort(); qrUriString += ":" + sharingUri.getPort();