diff --git a/app/src/basic/java/org/fdroid/fdroid/localrepo/SwapService.java b/app/src/basic/java/org/fdroid/fdroid/localrepo/SwapService.java new file mode 100644 index 000000000..534c7ac23 --- /dev/null +++ b/app/src/basic/java/org/fdroid/fdroid/localrepo/SwapService.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2018 Hans-Christoph Steiner + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 3 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +package org.fdroid.fdroid.localrepo; + +import android.content.Context; + +/** + * Dummy version for basic app flavor. + */ +public class SwapService { + public static void start(Context context) { + } +} diff --git a/app/src/full/java/org/fdroid/fdroid/localrepo/SwapService.java b/app/src/full/java/org/fdroid/fdroid/localrepo/SwapService.java index 1055ffd1c..08b4ac91f 100644 --- a/app/src/full/java/org/fdroid/fdroid/localrepo/SwapService.java +++ b/app/src/full/java/org/fdroid/fdroid/localrepo/SwapService.java @@ -22,6 +22,7 @@ import android.support.v4.app.NotificationCompat; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; import android.util.Log; +import cc.mvdan.accesspoint.WifiApControl; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.R; @@ -57,9 +58,11 @@ public class SwapService extends Service { private static final String KEY_APPS_TO_SWAP = "appsToSwap"; private static final String KEY_BLUETOOTH_ENABLED = "bluetoothEnabled"; private static final String KEY_WIFI_ENABLED = "wifiEnabled"; + private static final String KEY_HOTSPOT_ACTIVATED = "hotspotEnabled"; private static final String KEY_BLUETOOTH_ENABLED_BEFORE_SWAP = "bluetoothEnabledBeforeSwap"; private static final String KEY_BLUETOOTH_NAME_BEFORE_SWAP = "bluetoothNameBeforeSwap"; private static final String KEY_WIFI_ENABLED_BEFORE_SWAP = "wifiEnabledBeforeSwap"; + private static final String KEY_HOTSPOT_ACTIVATED_BEFORE_SWAP = "hotspotEnabledBeforeSwap"; @NonNull private final Set appsToSwap = new HashSet<>(); @@ -294,6 +297,14 @@ public class SwapService extends Service { swapPreferences.edit().putBoolean(SwapService.KEY_WIFI_ENABLED, visible).apply(); } + public static boolean getHotspotActivatedUserPreference() { + return swapPreferences.getBoolean(SwapService.KEY_HOTSPOT_ACTIVATED, false); + } + + public static void putHotspotActivatedUserPreference(boolean visible) { + swapPreferences.edit().putBoolean(SwapService.KEY_HOTSPOT_ACTIVATED, visible).apply(); + } + public static boolean wasBluetoothEnabledBeforeSwap() { return swapPreferences.getBoolean(SwapService.KEY_BLUETOOTH_ENABLED_BEFORE_SWAP, false); } @@ -318,6 +329,14 @@ public class SwapService extends Service { swapPreferences.edit().putBoolean(SwapService.KEY_WIFI_ENABLED_BEFORE_SWAP, visible).apply(); } + public static boolean wasHotspotEnabledBeforeSwap() { + return swapPreferences.getBoolean(SwapService.KEY_HOTSPOT_ACTIVATED_BEFORE_SWAP, false); + } + + public static void putHotspotEnabledBeforeSwap(boolean visible) { + swapPreferences.edit().putBoolean(SwapService.KEY_HOTSPOT_ACTIVATED_BEFORE_SWAP, visible).apply(); + } + private static final int NOTIFICATION = 1; private final Binder binder = new Binder(); @@ -370,8 +389,19 @@ public class SwapService extends Service { localBroadcastManager.registerReceiver(bonjourPeerRemoved, new IntentFilter(BonjourManager.ACTION_REMOVED)); localBroadcastManager.registerReceiver(localRepoStatus, new IntentFilter(LocalRepoService.ACTION_STATUS)); + if (getHotspotActivatedUserPreference()) { + WifiApControl wifiApControl = WifiApControl.getInstance(this); + if (wifiApControl != null) { + wifiApControl.enable(); + } + } else if (getWifiVisibleUserPreference()) { + if (wifiManager != null) { + wifiManager.setWifiEnabled(true); + } + } + BonjourManager.start(this); - BonjourManager.setVisible(this, getWifiVisibleUserPreference()); + BonjourManager.setVisible(this, getWifiVisibleUserPreference() || getHotspotActivatedUserPreference()); } /** @@ -382,7 +412,9 @@ public class SwapService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { deleteAllSwapRepos(); - startActivity(new Intent(this, SwapWorkflowActivity.class)); + Intent startUiIntent = new Intent(this, SwapWorkflowActivity.class); + startUiIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(startUiIntent); return START_NOT_STICKY; } @@ -413,6 +445,15 @@ public class SwapService extends Service { wifiManager.setWifiEnabled(false); } + WifiApControl ap = WifiApControl.getInstance(this); + if (ap != null) { + if (wasHotspotEnabledBeforeSwap()) { + ap.enable(); + } else { + ap.disable(); + } + } + stopPollingConnectedSwapRepo(); if (timer != null) { @@ -504,7 +545,7 @@ public class SwapService extends Service { if (hasIp) { LocalHTTPDManager.restart(this); BonjourManager.restart(this); - BonjourManager.setVisible(this, getWifiVisibleUserPreference()); + BonjourManager.setVisible(this, getWifiVisibleUserPreference() || getHotspotActivatedUserPreference()); } else { BonjourManager.stop(this); LocalHTTPDManager.stop(this); diff --git a/app/src/full/java/org/fdroid/fdroid/net/WifiStateChangeService.java b/app/src/full/java/org/fdroid/fdroid/net/WifiStateChangeService.java index 81f738c2a..e2326dd73 100644 --- a/app/src/full/java/org/fdroid/fdroid/net/WifiStateChangeService.java +++ b/app/src/full/java/org/fdroid/fdroid/net/WifiStateChangeService.java @@ -6,6 +6,7 @@ import android.content.Context; import android.content.Intent; import android.net.DhcpInfo; import android.net.NetworkInfo; +import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Build; @@ -13,10 +14,12 @@ import android.support.annotation.Nullable; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; import android.util.Log; +import cc.mvdan.accesspoint.WifiApControl; import org.apache.commons.net.util.SubnetUtils; import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.Preferences; +import org.fdroid.fdroid.R; import org.fdroid.fdroid.UpdateService; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Repo; @@ -89,8 +92,9 @@ public class WifiStateChangeService extends IntentService { NetworkInfo ni = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO); wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE); wifiState = wifiManager.getWifiState(); - if (ni == null || ni.isConnected()) { - Utils.debugLog(TAG, "ni == " + ni + " wifiState == " + printWifiState(wifiState)); + Utils.debugLog(TAG, "ni == " + ni + " wifiState == " + printWifiState(wifiState)); + if (ni == null + || ni.getState() == NetworkInfo.State.CONNECTED || ni.getState() == NetworkInfo.State.DISCONNECTED) { if (previousWifiState != wifiState && (wifiState == WifiManager.WIFI_STATE_ENABLED || wifiState == WifiManager.WIFI_STATE_DISABLING // might be switching to hotspot @@ -110,7 +114,6 @@ public class WifiStateChangeService extends IntentService { } public class WifiInfoThread extends Thread { - private static final String TAG = "WifiInfoThread"; @Override public void run() { @@ -129,7 +132,7 @@ public class WifiStateChangeService extends IntentService { if (wifiState == WifiManager.WIFI_STATE_ENABLED) { wifiInfo = wifiManager.getConnectionInfo(); FDroidApp.ipAddressString = formatIpAddress(wifiInfo.getIpAddress()); - setSsidFromWifiInfo(wifiInfo); + setSsid(wifiInfo); DhcpInfo dhcpInfo = wifiManager.getDhcpInfo(); if (dhcpInfo != null) { String netmask = formatIpAddress(dhcpInfo.netmask); @@ -147,14 +150,13 @@ public class WifiStateChangeService extends IntentService { setIpInfoFromNetworkInterface(); } } else if (wifiState == WifiManager.WIFI_STATE_DISABLED - || wifiState == WifiManager.WIFI_STATE_DISABLING) { + || wifiState == WifiManager.WIFI_STATE_DISABLING + || wifiState == WifiManager.WIFI_STATE_UNKNOWN) { // try once to see if its a hotspot setIpInfoFromNetworkInterface(); if (FDroidApp.ipAddressString == null) { return; } - } else { // a hotspot can be active during WIFI_STATE_UNKNOWN - setIpInfoFromNetworkInterface(); } if (retryCount > 120) { @@ -171,7 +173,7 @@ public class WifiStateChangeService extends IntentService { return; } - setSsidFromWifiInfo(wifiInfo); + setSsid(wifiInfo); String scheme; if (Preferences.get().isLocalRepoHttpsEnabled()) { @@ -226,16 +228,35 @@ public class WifiStateChangeService extends IntentService { } } - private void setSsidFromWifiInfo(WifiInfo wifiInfo) { - if (wifiInfo != null) { + private void setSsid(WifiInfo wifiInfo) { + if (wifiInfo != null && wifiInfo.getBSSID() != null) { String ssid = wifiInfo.getSSID(); Utils.debugLog(TAG, "Have wifi info, connected to " + ssid); - if (ssid != null) { + if (ssid == null) { + FDroidApp.ssid = getString(R.string.swap_blank_wifi_ssid); + } else { FDroidApp.ssid = ssid.replaceAll("^\"(.*)\"$", "$1"); } - String bssid = wifiInfo.getBSSID(); - if (bssid != null) { - FDroidApp.bssid = bssid; + FDroidApp.bssid = wifiInfo.getBSSID(); + } else { + WifiApControl wifiApControl = WifiApControl.getInstance(this); + Utils.debugLog(TAG, "WifiApControl: " + wifiApControl); + if (wifiApControl == null && FDroidApp.ipAddressString != null) { + wifiInfo = wifiManager.getConnectionInfo(); + if (wifiInfo != null && wifiInfo.getBSSID() != null) { + setSsid(wifiInfo); + } else { + FDroidApp.ssid = getString(R.string.swap_active_hotspot, ""); + } + } else if (wifiApControl != null && wifiApControl.isEnabled()) { + WifiConfiguration wifiConfiguration = wifiApControl.getConfiguration(); + Utils.debugLog(TAG, "WifiConfiguration: " + wifiConfiguration); + if (wifiConfiguration.hiddenSSID) { + FDroidApp.ssid = getString(R.string.swap_hidden_wifi_ssid); + } else { + FDroidApp.ssid = wifiConfiguration.SSID; + } + FDroidApp.bssid = wifiConfiguration.BSSID; } } } diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/StartSwapView.java b/app/src/full/java/org/fdroid/fdroid/views/swap/StartSwapView.java index 1da4701e3..36a33c989 100644 --- a/app/src/full/java/org/fdroid/fdroid/views/swap/StartSwapView.java +++ b/app/src/full/java/org/fdroid/fdroid/views/swap/StartSwapView.java @@ -223,6 +223,10 @@ public class StartSwapView extends SwapView { WifiApControl wifiAp = WifiApControl.getInstance(getActivity()); if (wifiAp != null && wifiAp.isWifiApEnabled()) { WifiConfiguration config = wifiAp.getConfiguration(); + TextView textWifiVisible = findViewById(R.id.wifi_visible); + if (textWifiVisible != null) { + textWifiVisible.setText(R.string.swap_visible_hotspot); + } viewWifiNetwork.setText(getContext().getString(R.string.swap_active_hotspot, config.SSID)); } else if (TextUtils.isEmpty(FDroidApp.ssid)) { // not connected to or setup with any wifi network diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index c4a5ed457..6ae9d384c 100644 --- a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -117,6 +117,7 @@ public class SwapWorkflowActivity extends AppCompatActivity { private NewRepoConfig confirmSwapConfig; private LocalBroadcastManager localBroadcastManager; private WifiManager wifiManager; + private WifiApControl wifiApControl; private BluetoothAdapter bluetoothAdapter; @LayoutRes @@ -227,6 +228,7 @@ public class SwapWorkflowActivity extends AppCompatActivity { new IntentFilter(Downloader.ACTION_INTERRUPTED)); wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE); + wifiApControl = WifiApControl.getInstance(this); bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); @@ -442,12 +444,19 @@ public class SwapWorkflowActivity extends AppCompatActivity { } private void setupWifiAP() { - WifiApControl ap = WifiApControl.getInstance(this); + if (wifiApControl == null) { + Log.e(TAG, "WiFi AP is null"); + Toast.makeText(this, R.string.swap_toast_could_not_enable_hotspot, Toast.LENGTH_LONG).show(); + return; + } + SwapService.putHotspotEnabledBeforeSwap(wifiApControl.isEnabled()); wifiManager.setWifiEnabled(false); - if (ap.enable()) { + if (wifiApControl.enable()) { Toast.makeText(this, R.string.swap_toast_hotspot_enabled, Toast.LENGTH_SHORT).show(); + SwapService.putHotspotActivatedUserPreference(true); } else { Toast.makeText(this, R.string.swap_toast_could_not_enable_hotspot, Toast.LENGTH_LONG).show(); + SwapService.putHotspotActivatedUserPreference(false); Log.e(TAG, "Could not enable WiFi AP."); } } @@ -986,7 +995,11 @@ public class SwapWorkflowActivity extends AppCompatActivity { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { Context context = getApplicationContext(); if (isChecked) { - wifiManager.setWifiEnabled(true); + if (wifiApControl != null && wifiApControl.isEnabled()) { + setupWifiAP(); + } else { + wifiManager.setWifiEnabled(true); + } BonjourManager.start(context); } BonjourManager.setVisible(context, isChecked); @@ -1040,7 +1053,11 @@ public class SwapWorkflowActivity extends AppCompatActivity { peopleNearbyProgress.setVisibility(View.VISIBLE); break; case BonjourManager.STATUS_VISIBLE: - textWifiVisible.setText(R.string.swap_visible_wifi); + if (wifiApControl != null && wifiApControl.isEnabled()) { + textWifiVisible.setText(R.string.swap_visible_hotspot); + } else { + textWifiVisible.setText(R.string.swap_visible_wifi); + } peopleNearbyText.setText(R.string.swap_scanning_for_peers); peopleNearbyText.setVisibility(View.VISIBLE); peopleNearbyProgress.setVisibility(View.VISIBLE); diff --git a/app/src/full/res/layout/swap_join_wifi.xml b/app/src/full/res/layout/swap_join_wifi.xml index 6cce7801b..7cc0244fe 100644 --- a/app/src/full/res/layout/swap_join_wifi.xml +++ b/app/src/full/res/layout/swap_join_wifi.xml @@ -39,7 +39,7 @@ diff --git a/app/src/main/java/org/fdroid/fdroid/Preferences.java b/app/src/main/java/org/fdroid/fdroid/Preferences.java index d6f455453..6fc26abbd 100644 --- a/app/src/main/java/org/fdroid/fdroid/Preferences.java +++ b/app/src/main/java/org/fdroid/fdroid/Preferences.java @@ -436,7 +436,8 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh } public boolean isOnDemandDownloadAllowed() { - if (FDroidApp.networkState == ConnectivityMonitorService.FLAG_NET_NO_LIMIT) { + if (FDroidApp.networkState == ConnectivityMonitorService.FLAG_NET_NO_LIMIT + || FDroidApp.networkState == ConnectivityMonitorService.FLAG_NET_DEVICE_AP_WITHOUT_INTERNET) { return getOverWifi() != OVER_NETWORK_NEVER; } else if (FDroidApp.networkState == ConnectivityMonitorService.FLAG_NET_METERED) { return getOverData() != OVER_NETWORK_NEVER; diff --git a/app/src/main/java/org/fdroid/fdroid/net/ConnectivityMonitorService.java b/app/src/main/java/org/fdroid/fdroid/net/ConnectivityMonitorService.java index 0bfd2bc04..f805bdf63 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/ConnectivityMonitorService.java +++ b/app/src/main/java/org/fdroid/fdroid/net/ConnectivityMonitorService.java @@ -9,10 +9,17 @@ import android.net.NetworkInfo; import android.os.Build; import android.support.annotation.NonNull; import android.support.v4.app.JobIntentService; +import android.util.Log; import com.nostra13.universalimageloader.core.ImageLoader; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.Preferences; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; + /** * An {@link JobIntentService} subclass for tracking whether there is metered or * unmetered internet available, based on @@ -30,6 +37,7 @@ public class ConnectivityMonitorService extends JobIntentService { public static final int FLAG_NET_UNAVAILABLE = 0; public static final int FLAG_NET_METERED = 1; public static final int FLAG_NET_NO_LIMIT = 2; + public static final int FLAG_NET_DEVICE_AP_WITHOUT_INTERNET = 3; private static final String ACTION_START = "org.fdroid.fdroid.net.action.CONNECTIVITY_MONITOR"; @@ -59,7 +67,14 @@ public class ConnectivityMonitorService extends JobIntentService { /** * Gets the state of internet availability, whether there is no connection at all, * whether the connection has no usage limit (like most WiFi), or whether this is - * a metered connection like most cellular plans or hotspot WiFi connections. + * a metered connection like most cellular plans or hotspot WiFi connections. This + * also detects whether the device has a hotspot AP enabled but the mobile + * connection does not provide internet. That is a special case that is useful + * for nearby swapping, but nothing else. + *

+ * {@link NullPointerException}s are ignored in the hotspot detection since that + * detection should not affect normal usage at all, and there are often weird + * cases when looking through the network devices, especially on bad ROMs. */ public static int getNetworkState(Context context) { ConnectivityManager cm = (ConnectivityManager) context.getSystemService(CONNECTIVITY_SERVICE); @@ -67,6 +82,31 @@ public class ConnectivityMonitorService extends JobIntentService { return FLAG_NET_UNAVAILABLE; } NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + + if (activeNetwork == null && Build.VERSION.SDK_INT >= 21 && cm.getAllNetworks().length == 0) { + try { + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + while (networkInterfaces.hasMoreElements()) { + NetworkInterface netIf = networkInterfaces.nextElement(); + if (netIf.getDisplayName().contains("wlan0") + || netIf.getDisplayName().contains("eth0") + || netIf.getDisplayName().contains("ap0")) { + for (Enumeration addr = netIf.getInetAddresses(); addr.hasMoreElements();) { + InetAddress inetAddress = addr.nextElement(); + if (inetAddress.isLoopbackAddress() || inetAddress instanceof Inet6Address) { + continue; + } + Log.i(TAG, "FLAG_NET_DEVICE_AP_WITHOUT_INTERNET: " + netIf.getDisplayName() + + " " + inetAddress); + return FLAG_NET_DEVICE_AP_WITHOUT_INTERNET; // NOPMD + } + } + } + } catch (SocketException | NullPointerException e) { // NOPMD + // ignored + } + } + if (activeNetwork == null || !activeNetwork.isConnected()) { return FLAG_NET_UNAVAILABLE; } diff --git a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java index ff7d1012a..e4ea38c39 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java @@ -57,6 +57,7 @@ import org.fdroid.fdroid.UpdateService; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.NewRepoConfig; import org.fdroid.fdroid.localrepo.SDCardScannerService; +import org.fdroid.fdroid.localrepo.SwapService; import org.fdroid.fdroid.net.WifiStateChangeService; import org.fdroid.fdroid.views.AppDetailsActivity; import org.fdroid.fdroid.views.ManageReposActivity; @@ -250,7 +251,7 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_LOCATION_PERMISSIONS) { WifiStateChangeService.start(this, null); - startActivity(new Intent(this, SwapWorkflowActivity.class)); + SwapService.start(this); } else if (requestCode == REQUEST_STORAGE_PERMISSIONS) { Toast.makeText(this, this.getString(R.string.scan_removable_storage_toast, ""), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 538b62c0b..bb2c85a25 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -477,6 +477,11 @@ This often occurs with apps installed via Google Play or other sources, if they Swap success! No network yet %1$s (your hotspot) + Visible via hotspot + (blank) + (hidden) + Setting up hotspot… + Stopping hotspot… Tap to open available networks Tap to switch to a Wi-Fi network Open QR Scanner