Skip to content
Snippets Groups Projects
Verified Commit 915183f9 authored by Torsten Grote's avatar Torsten Grote
Browse files

Start a web server after starting the hotspot

that serves its own app as a download
parent 7c66da3d
No related branches found
No related tags found
1 merge request!1Add web server for app downloading and small fixes
......@@ -27,4 +27,5 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'com.google.zxing:core:3.4.0'
implementation 'org.nanohttpd:nanohttpd:2.3.1'
}
......@@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
package="org.briarproject.hotspot">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
......
......@@ -14,6 +14,8 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import org.briarproject.hotspot.MainViewModel.WebServerState;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION.SDK_INT;
......@@ -59,7 +61,6 @@ public class HotspotFragment extends Fragment {
passwordView.setText("");
button.setText(R.string.start_hotspot);
button.setEnabled(true);
serverButton.setVisibility(GONE);
hotspotStarted = false;
} else {
String qrCodeText = createWifiLoginString(config.ssid, config.password,
......@@ -75,7 +76,6 @@ public class HotspotFragment extends Fragment {
passwordView.setText(getString(R.string.password, config.password));
button.setText(R.string.stop_hotspot);
button.setEnabled(true);
serverButton.setVisibility(VISIBLE);
hotspotStarted = true;
}
});
......@@ -84,6 +84,16 @@ public class HotspotFragment extends Fragment {
if (SDK_INT >= 29 && (checkSelfPermission(requireContext(), ACCESS_FINE_LOCATION) != PERMISSION_GRANTED)) {
requestPermissions(new String[]{ACCESS_FINE_LOCATION}, 0);
}
viewModel.getWebServerState().observe(getViewLifecycleOwner(), state -> {
if (state == WebServerState.STOPPED) {
serverButton.setVisibility(GONE);
} else if (state == WebServerState.STARTED) {
serverButton.setVisibility(VISIBLE);
} else if (state == WebServerState.ERROR) {
statusView.setText(R.string.web_server_error);
}
});
}
public void onButtonClick(View view) {
......
......@@ -15,6 +15,8 @@ import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import java.io.IOException;
import static android.content.Context.WIFI_P2P_SERVICE;
import static android.content.Context.WIFI_SERVICE;
import static android.net.wifi.WifiManager.WIFI_MODE_FULL;
......@@ -27,12 +29,15 @@ public class MainViewModel extends AndroidViewModel {
private final MutableLiveData<NetworkConfig> config = new MutableLiveData<>();
private final MutableLiveData<String> status = new MutableLiveData<>();
private final MutableLiveData<WebServerState> webServerState =
new MutableLiveData<>(WebServerState.STOPPED);
private final Application app;
private final String lockTag;
private final WifiManager wifiManager;
private final WifiP2pManager wifiP2pManager;
private final Handler handler;
private final WebServer webServer;
private WifiLock wifiLock;
private Channel channel;
......@@ -44,6 +49,7 @@ public class MainViewModel extends AndroidViewModel {
wifiManager = (WifiManager) app.getSystemService(WIFI_SERVICE);
wifiP2pManager = (WifiP2pManager) app.getSystemService(WIFI_P2P_SERVICE);
handler = new Handler(app.getMainLooper());
webServer = new WebServer(app);
}
LiveData<NetworkConfig> getWifiConfiguration() {
......@@ -54,6 +60,10 @@ public class MainViewModel extends AndroidViewModel {
return status;
}
LiveData<WebServerState> getWebServerState() {
return webServerState;
}
void startWifiP2pHotspot() {
if (wifiP2pManager == null) {
status.setValue(app.getString(R.string.no_wifi_direct));
......@@ -100,6 +110,7 @@ public class MainViewModel extends AndroidViewModel {
config.setValue(new NetworkConfig(group.getNetworkName(), group.getPassphrase(),
true));
status.setValue(app.getString(R.string.callback_started));
startWebServer();
}
};
try {
......@@ -110,7 +121,8 @@ public class MainViewModel extends AndroidViewModel {
}
private void releaseWifiP2pHotspot(String statusMessage) {
if (SDK_INT >= 27) channel.close();
stopWebServer();
if (SDK_INT >= 27) channel.close();
channel = null;
releaseLock();
config.setValue(null);
......@@ -150,6 +162,23 @@ public class MainViewModel extends AndroidViewModel {
wifiLock.release();
}
private void startWebServer() {
try {
webServer.start();
webServerState.postValue(WebServerState.STARTED);
} catch (IOException e) {
e.printStackTrace();
webServerState.postValue(WebServerState.ERROR);
}
}
private void stopWebServer() {
webServer.stop();
webServerState.postValue(WebServerState.STOPPED);
}
enum WebServerState { STOPPED, STARTED, ERROR }
static class NetworkConfig {
final String ssid, password;
......
package org.briarproject.hotspot;
import android.app.Application;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import fi.iki.elonen.NanoHTTPD;
import static fi.iki.elonen.NanoHTTPD.Response.Status.NOT_FOUND;
import static fi.iki.elonen.NanoHTTPD.Response.Status.OK;
public class WebServer extends NanoHTTPD {
private final Application ctx;
public WebServer(Application ctx) {
super(9999);
this.ctx = ctx;
}
public void start() throws IOException {
start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
}
@Override
public Response serve(IHTTPSession session) {
if (session.getUri().endsWith("app.apk")) {
return serveApk();
} else {
String msg = "<html><body><h1>Download Offline Hotspot App</h1>\n";
msg += "<h2><a href=\"/app.apk\">Click here to download</a></h2>";
msg += "After download is complete, open the downloaded file and install it.";
return newFixedLengthResponse(msg + "</body></html>\n");
}
}
private Response serveApk() {
String mime = "application/vnd.android.package-archive";
File file = new File(ctx.getPackageCodePath());
long fileLen = file.length();
Response res;
try {
FileInputStream fis = new FileInputStream(file);
res = newFixedLengthResponse(OK, mime, fis, fileLen);
res.addHeader("Content-Length", "" + fileLen);
} catch (FileNotFoundException e) {
e.printStackTrace();
res = newFixedLengthResponse(NOT_FOUND, MIME_PLAINTEXT, "Error 404, file not found.");
}
return res;
}
}
......@@ -15,4 +15,5 @@
<string name="no_wifi_direct">Device does not support Wi-Fi Direct</string>
<string name="callback_permission_denied">Location permission was denied</string>
<string name="connected">Peer connected</string>
<string name="web_server_error">Error starting web server!</string>
</resources>
......@@ -18,3 +18,5 @@ android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
# Set testOnly to false, so debug app can be installed on recipient phones
android.injected.testOnly=false
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment