Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • akwizgran/hotspot
  • grote/hotspot
  • briar/hotspot
3 results
Show changes
Showing
with 458 additions and 49 deletions
package org.briarproject.hotspot;
import java.security.SecureRandom;
import java.util.Random;
class StringUtils {
private static final Random random = new SecureRandom();
private static String digits = "123456789"; // avoid 0
private static String letters = "abcdefghijkmnopqrstuvwxyz"; // avoid l
private static String LETTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ"; // avoid I, O
public static String getRandomString(int length) {
char[] c = new char[length];
for (int i = 0; i < length; i++) {
if (random.nextBoolean()) {
c[i] = random(digits);
} else if (random.nextBoolean()) {
c[i] = random(letters);
} else {
c[i] = random(LETTERS);
}
}
return new String(c);
}
private static char random(String universe) {
return universe.charAt(random.nextInt(universe.length()));
}
}
package org.briarproject.hotspot;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.FragmentActivity;
import static android.content.Intent.CATEGORY_DEFAULT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static org.briarproject.hotspot.BuildConfig.APPLICATION_ID;
class UiUtils {
static DialogInterface.OnClickListener getGoToSettingsListener(
Context context) {
return (dialog, which) -> {
Intent i = new Intent();
i.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
i.addCategory(CATEGORY_DEFAULT);
i.setData(Uri.parse("package:" + APPLICATION_ID));
i.addFlags(FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
};
}
static void showDenialDialog(FragmentActivity ctx, @StringRes int title,
@StringRes int body, DialogInterface.OnClickListener onOkClicked,
Runnable onDismiss) {
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle(title);
builder.setMessage(body);
builder.setPositiveButton(R.string.ok, onOkClicked);
builder.setNegativeButton(R.string.cancel,
(dialog, which) -> ctx.supportFinishAfterTransition());
builder.setOnDismissListener(dialog -> onDismiss.run());
builder.show();
}
static void showRationale(Context ctx, @StringRes int title,
@StringRes int body,
Runnable onContinueClicked, Runnable onDismiss) {
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.setTitle(title);
builder.setMessage(body);
builder.setNeutralButton(R.string.continue_button,
(dialog, which) -> onContinueClicked.run());
builder.setOnDismissListener(dialog -> onDismiss.run());
builder.show();
}
}
package org.briarproject.hotspot;
import android.content.Context;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import androidx.annotation.Nullable;
import fi.iki.elonen.NanoHTTPD;
import static android.util.Xml.Encoding.UTF_8;
import static fi.iki.elonen.NanoHTTPD.Response.Status.INTERNAL_ERROR;
import static fi.iki.elonen.NanoHTTPD.Response.Status.NOT_FOUND;
import static fi.iki.elonen.NanoHTTPD.Response.Status.OK;
import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.hotspot.BuildConfig.VERSION_NAME;
import static org.briarproject.hotspot.LogUtils.logException;
public class WebServer extends NanoHTTPD {
final static int PORT = 9999;
private static final Logger LOG = getLogger(WebServer.class.getName());
private static final String FILE_HTML = "hotspot.html";
private static final Pattern REGEX_AGENT =
Pattern.compile("Android ([0-9]+)");
private final Context ctx;
public WebServer(Context ctx) {
super(PORT);
this.ctx = ctx;
}
public void start() throws IOException {
start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
}
@Override
public Response serve(IHTTPSession session) {
if (session.getUri().endsWith("favicon.ico")) {
return newFixedLengthResponse(NOT_FOUND, MIME_PLAINTEXT,
NOT_FOUND.getDescription());
}
if (session.getUri().endsWith(".apk")) {
return serveApk();
}
Response res;
try {
String html = getHtml(session.getHeaders().get("user-agent"));
res = newFixedLengthResponse(OK, MIME_HTML, html);
} catch (Exception e) {
logException(LOG, WARNING, e);
res = newFixedLengthResponse(INTERNAL_ERROR, MIME_PLAINTEXT,
INTERNAL_ERROR.getDescription());
}
return res;
}
private String getHtml(@Nullable String userAgent) throws Exception {
Document doc;
try (InputStream is = ctx.getAssets().open(FILE_HTML)) {
doc = Jsoup.parse(is, UTF_8.name(), "");
}
String app = ctx.getString(R.string.app_name);
String appV = app + " " + VERSION_NAME;
doc.select("#download_title").first()
.text(ctx.getString(R.string.website_download_title, appV));
doc.select("#download_intro").first()
.text(ctx.getString(R.string.website_download_intro, app));
doc.select("#download_button").first()
.text(ctx.getString(R.string.website_download_title, app));
doc.select("#download_outro").first()
.text(ctx.getString(R.string.website_download_outro));
doc.select("#troubleshooting_title").first()
.text(ctx.getString(R.string.website_troubleshooting_title));
doc.select("#troubleshooting_1").first()
.text(ctx.getString(R.string.website_troubleshooting_1));
doc.select("#troubleshooting_2").first()
.text(getUnknownSourcesString(userAgent));
return doc.outerHtml();
}
private String getUnknownSourcesString(String userAgent) {
boolean is8OrHigher = false;
if (userAgent != null) {
Matcher matcher = REGEX_AGENT.matcher(userAgent);
if (matcher.find()) {
int androidMajorVersion =
Integer.parseInt(requireNonNull(matcher.group(1)));
is8OrHigher = androidMajorVersion >= 8;
}
}
return is8OrHigher ?
ctx.getString(R.string.website_troubleshooting_2_new) :
ctx.getString(R.string.website_troubleshooting_2_old);
}
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) {
logException(LOG, WARNING, e);
res = newFixedLengthResponse(NOT_FOUND, MIME_PLAINTEXT,
"Error 404, file not found.");
}
return res;
}
}
package org.briarproject.hotspot;
import android.content.Context;
import java.io.IOException;
import java.net.InetAddress;
import java.util.logging.Logger;
import androidx.annotation.WorkerThread;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.hotspot.LogUtils.logException;
import static org.briarproject.hotspot.NetworkUtils.getAccessPointAddress;
import static org.briarproject.hotspot.WebServer.PORT;
class WebServerManager {
interface WebServerListener {
@WorkerThread
void onWebServerStarted(String url);
@WorkerThread
void onWebServerError();
}
private static final Logger LOG =
getLogger(WebServerManager.class.getName());
private final WebServer webServer;
private final WebServerListener listener;
WebServerManager(Context ctx, WebServerListener listener) {
this.listener = listener;
webServer = new WebServer(ctx);
}
@WorkerThread
void startWebServer() {
try {
webServer.start();
onWebServerStarted();
} catch (IOException e) {
logException(LOG, WARNING, e);
listener.onWebServerError();
}
}
private void onWebServerStarted() {
String url = "http://192.168.49.1:" + PORT;
InetAddress address = getAccessPointAddress();
if (address == null) {
LOG.info(
"Could not find access point address, assuming 192.168.49.1");
} else {
if (LOG.isLoggable(INFO)) {
LOG.info("Access point address " + address.getHostAddress());
}
url = "http://" + address.getHostAddress() + ":" + PORT;
}
listener.onWebServerStarted(url);
}
/**
* It is safe to call this more than once and it won't throw.
*/
void stopWebServer() {
webServer.stop();
}
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fragment_container"
android:name="org.briarproject.hotspot.HotspotFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:gravity="center"
android:orientation="vertical"
tools:context=".MainActivity">
<ImageView
android:id="@+id/qr_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/qr_code_description"
android:visibility="gone" />
<TextView
android:id="@+id/ssid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp" />
<TextView
android:id="@+id/password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:onClick="onButtonClick"
android:text="@string/start_hotspot" />
<TextView
android:id="@+id/status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp" />
</LinearLayout>
\ No newline at end of file
tools:context=".MainActivity" />
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:fillViewport="true"
android:orientation="vertical"
tools:context=".HotspotFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
tools:context=".HotspotFragment">
<ImageView
android:id="@+id/qr_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/qr_code_description"
android:visibility="gone" />
<TextView
android:id="@+id/ssid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp" />
<TextView
android:id="@+id/password"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/start_hotspot" />
<TextView
android:id="@+id/status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:textSize="16sp"
tools:text="@string/stop_framework_busy" />
<Button
android:id="@+id/serverButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/connected"
android:visibility="gone"
tools:visibility="visible" />
</LinearLayout>
</ScrollView>
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="monospace"
android:padding="16dp"
tools:text="@tools:sample/lorem/random" />
</ScrollView>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:gravity="center"
android:orientation="vertical"
tools:context=".ServerFragment">
<ImageView
android:id="@+id/qr_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/qr_code_description"
android:visibility="gone"
tools:src="@tools:sample/avatars"
tools:visibility="visible" />
<TextView
android:id="@+id/info"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:gravity="center"
android:textSize="18sp"
android:text="@string/server_info" />
<TextView
android:id="@+id/url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:textSize="18sp"
tools:text="http://192.168.49.1:9999" />
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/interfaces"
android:title="@string/menu_interfaces"
app:showAsAction="never" />
</menu>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>
\ No newline at end of file
<resources>
<string name="app_name">Offline Hotspot</string>
<string name="ok">OK</string>
<string name="cancel">Cancel</string>
<string name="continue_button">Continue</string>
<string name="permission_location_title">Location permission</string>
<string name="permission_hotspot_location_request_body">To create a Wi-Fi hotspot, Briar needs permission to access your location.\n\nBriar does not store your location or share it with anyone.</string>
<string name="permission_hotspot_location_denied_body">You have denied access to your location, but Briar needs this permission to create a Wi-Fi hotspot.\n\nPlease consider granting access.</string>
<string name="wifi_settings_title">Wi-Fi setting</string>
<string name="wifi_settings_request_enable_body">To create a Wi-Fi hotspot, Briar needs to use Wi-Fi. Please enable it.</string>
<string name="wifi_settings_request_denied_body">You have denied to enable Wi-Fi, but Briar needs to use Wi-Fi.\n\nPlease consider enabling it.</string>
<string name="menu_interfaces">Network interfaces</string>
<string name="start_hotspot">Start hotspot</string>
<string name="stop_hotspot">Stop hotspot</string>
<string name="ssid">Name: %s</string>
<string name="password">Password: %s</string>
<string name="starting_hotspot">Starting hotspot</string>
<string name="callback_started">Hotspot started</string>
<string name="callback_failed">Hotspot failed to start: error code %d</string>
<string name="callback_waiting">Waiting for hotspot to start</string>
<string name="callback_no_group_info">Hotspot failed to start: no group info</string>
<string name="start_callback_started">Hotspot started</string>
<string name="start_callback_started_freq">Hotspot started (%1$.2f GHz)</string>
<string name="start_callback_failed">Hotspot failed to start: error %s</string>
<string name="start_callback_failed_unknown">Hotspot failed to start with an unknown error, reason %d</string>
<string name="start_callback_no_group_info">Hotspot failed to start: no group info</string>
<string name="start_no_attempts_left">Hotspot failed to start with an unknown error</string>
<string name="stop_callback_failed">Unknown error while stopping hotspot (reason %d)</string>
<string name="stop_framework_busy">Unable to start the hotspot. If you have another hotspot running or are sharing your internet connection via Wifi, try stopping that and try again afterwards.</string>
<string name="hotspot_stopped">Hotspot stopped</string>
<string name="qr_code_description">QR code with Wi-Fi login details</string>
<string name="no_wifi_manager">Device does not support Wi-Fi</string>
<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="wifi_5ghz_supported">5Ghz Wi-Fi is supported</string>
<string name="connected">Peer has connected</string>
<string name="connected_toast">Peer has connected, press button for download info</string>
<string name="web_server_error">Error starting web server!</string>
<string name="server_info">Visit this site on the other phone either by scanning the QR code or by typing this link manually.</string>
<!-- e.g. Download Briar 1.2.20 -->
<string name="website_download_title">Download %s</string>
<string name="website_download_intro">Someone nearby shared %s with you.</string>
<string name="website_download_outro">After the download is complete, open the downloaded file and install it.</string>
<string name="website_troubleshooting_title">Troubleshooting</string>
<string name="website_troubleshooting_1">If you cannot download the app, try it with a different web browser app.</string>
<string name="website_troubleshooting_2_old">To install the downloaded app, you might need to allow installation of apps from \"Unknown sources\" in system settings. Afterwards, you may need to download the app again.</string>
<string name="website_troubleshooting_2_new">To install the downloaded app, you might need to allow your browser to install unknown apps.</string>
</resources>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>
......@@ -4,7 +4,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath 'com.android.tools.build:gradle:4.1.3'
}
}
......
......@@ -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