I tried to get nanohttpd working under android. I have used this example:
package com.example.android_test;
import java.io.IOException;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import com.example.android_test.NanoHTTPD.Response.Status;
import android.app.Activity;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.widget.TextView;
public class MainActivity extends Activity {
private static final int PORT = 8765;
private TextView hello;
private MyHTTPD server;
private Handler handler = new Handler();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
hello = (TextView) findViewById(R.id.hello);
}
#Override
protected void onResume() {
super.onResume();
TextView textIpaddr = (TextView) findViewById(R.id.ipaddr);
WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
int ipAddress = wifiManager.getConnectionInfo().getIpAddress();
final String formatedIpAddress = String.format("%d.%d.%d.%d", (ipAddress & 0xff),
(ipAddress >> 8 & 0xff), (ipAddress >> 16 & 0xff), (ipAddress >> 24 & 0xff));
textIpaddr.setText("Please access! http://" + formatedIpAddress + ":" + PORT);
try {
server = new MyHTTPD();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
protected void onPause() {
super.onPause();
if (server != null)
server.stop();
}
private class MyHTTPD extends NanoHTTPD {
public MyHTTPD() throws IOException {
super(PORT);
}
#Override
public Response serve(String uri, Method method, Map<String, String> headers,
Map<String, String> parms, Map<String, String> files) {
final StringBuilder buf = new StringBuilder();
for (Entry<String, String> kv : headers.entrySet())
buf.append(kv.getKey() + " : " + kv.getValue() + "\n");
handler.post(new Runnable() {
#Override
public void run() {
hello.setText(buf);
}
});
final String html = "<html><head><head><body><h1>Hello, World</h1></body></html>";
return new NanoHTTPD.Response(Status.OK, MIME_HTML, html);
}
}
}
When i start the application on my phone the activity shows the proper ip address. I also can ping the shown address. But when i try to access this via a browser the site will not load. In addition the the above shown MainActivity.java i have only added the NanoHTTPD.java file from the nanohttpd project. Any ideas?
Two things come to mind, both are permissions related. Take a look at your android manifest, you'll want two permissions for your app
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
INTERNET, because you're accessing network services, and WRITE_EXTERNAL_STORAGE because when NanoHttpd receives an incoming connection it writes temp files and most / all phones map the "java.io.tmpdir" to point at the SD card.
See if that helps.
Related
I'm doing Android Things project.
I want to publish a String message to Google Cloud IoT Core, but there are errors shown.
I'm using Raspberry Pi 3 with Android Things OS and and programming it using Android Studio.
Errors screenshot:
This is the whole code:
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cacaosd.com.sample1">
<!-- PAHO Permissions -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<!-- PAHO Permissions -->
<application>
<uses-library android:name="com.google.android.things"/>
<!-- Mqtt Service -->
<service android:name="org.eclipse.paho.android.service.MqttService" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.IOT_LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
</manifest>
IotCoreCommunicator Class
package cacaosd.com.sample1;
import android.content.Context;
import android.util.Log;
import java.util.concurrent.TimeUnit;
import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
public class IotCoreCommunicator {
private static final String SERVER_URI = "ssl://mqtt.googleapis.com:8883";
public static class Builder {
private Context context;
private String projectId;
private String cloudRegion;
private String registryId;
private String deviceId;
private int privateKeyRawFileId;
public Builder withContext(Context context) {
this.context = context;
return this;
}
public Builder withProjectId(String projectId) {
this.projectId = projectId;
return this;
}
public Builder withCloudRegion(String cloudRegion) {
this.cloudRegion = cloudRegion;
return this;
}
public Builder withRegistryId(String registryId) {
this.registryId = registryId;
return this;
}
public Builder withDeviceId(String deviceId) {
this.deviceId = deviceId;
return this;
}
public Builder withPrivateKeyRawFileId(int privateKeyRawFileId) {
this.privateKeyRawFileId = privateKeyRawFileId;
return this;
}
public IotCoreCommunicator build() {
if (context == null) {
throw new IllegalStateException("context must not be null");
}
if (projectId == null) {
throw new IllegalStateException("projectId must not be null");
}
if (cloudRegion == null) {
throw new IllegalStateException("cloudRegion must not be null");
}
if (registryId == null) {
throw new IllegalStateException("registryId must not be null");
}
if (deviceId == null) {
throw new IllegalStateException("deviceId must not be null");
}
String clientId = "projects/" + projectId + "/locations/" + cloudRegion + "/registries/" + registryId + "/devices/" + deviceId;
if (privateKeyRawFileId == 0) {
throw new IllegalStateException("privateKeyRawFileId must not be 0");
}
MqttAndroidClient client = new MqttAndroidClient(context, SERVER_URI, clientId);
IotCorePasswordGenerator passwordGenerator = new IotCorePasswordGenerator(projectId, context.getResources(), privateKeyRawFileId);
return new IotCoreCommunicator(client, deviceId, passwordGenerator);
}
}
private final MqttAndroidClient client;
private final String deviceId;
private final IotCorePasswordGenerator passwordGenerator;
IotCoreCommunicator(MqttAndroidClient client, String deviceId, IotCorePasswordGenerator passwordGenerator) {
this.client = client;
this.deviceId = deviceId;
this.passwordGenerator = passwordGenerator;
}
public void connect() {
monitorConnection();
clientConnect();
subscribeToConfigChanges();
}
private void monitorConnection() {
client.setCallback(new MqttCallback() {
#Override
public void connectionLost(Throwable cause) {
Log.e("TUT", "connection lost", cause);
}
#Override
public void messageArrived(String topic, MqttMessage message) throws Exception {
Log.d("TUT", "message arrived " + topic + " MSG " + message);
// You need to do something with messages when they arrive
}
#Override
public void deliveryComplete(IMqttDeliveryToken token) {
Log.d("TUT", "delivery complete " + token);
}
});
}
private void clientConnect() {
try {
MqttConnectOptions connectOptions = new MqttConnectOptions();
// Note that the the Google Cloud IoT Core only supports MQTT 3.1.1, and Paho requires that we explicitly set this.
// If you don't, the server will immediately close its connection to your device.
connectOptions.setMqttVersion(MqttConnectOptions.MQTT_VERSION_3_1_1);
// With Google Cloud IoT Core, the username field is ignored, however it must be set for the
// Paho client library to send the password field. The password field is used to transmit a JWT to authorize the device.
connectOptions.setUserName("unused-but-necessary");
connectOptions.setPassword(passwordGenerator.createJwtRsaPassword());
IMqttToken iMqttToken = client.connect(connectOptions);
iMqttToken.setActionCallback(new IMqttActionListener() {
#Override
public void onSuccess(IMqttToken asyncActionToken) {
Log.d("TUT", "success, connected");
}
#Override
public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
Log.e("TUT", "failure, not connected", exception);
}
});
iMqttToken.waitForCompletion(TimeUnit.SECONDS.toMillis(30));
Log.d("TUT", "IoT Core connection established.");
} catch (MqttException e) {
throw new IllegalStateException(e);
}
}
/**
* Configuration is managed and sent from the IoT Core Platform
*/
private void subscribeToConfigChanges() {
try {
client.subscribe("/devices/" + deviceId + "/config", 1);
} catch (MqttException e) {
throw new IllegalStateException(e);
}
}
public void publishMessage(String subtopic, String message) {
String topic = "/devices/" + deviceId + "/" + subtopic;
String payload = "{msg:\"" + message + "\"}";
MqttMessage mqttMessage = new MqttMessage(payload.getBytes());
mqttMessage.setQos(1);
try {
client.publish(topic, mqttMessage);
Log.d("TUT", "IoT Core message published. To topic: " + topic);
} catch (MqttException e) {
throw new IllegalStateException(e);
}
}
public void disconnect() {
try {
Log.d("TUT", "IoT Core connection disconnected.");
client.disconnect();
} catch (MqttException e) {
throw new IllegalStateException(e);
}
}
}
IotCorePasswordGenerator Class
package cacaosd.com.sample1;
import android.content.res.Resources;
import android.util.Base64;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
class IotCorePasswordGenerator {
private final String projectId;
private final Resources resources;
private final int privateKeyRawFileId;
IotCorePasswordGenerator(String projectId, Resources resources, int privateKeyRawFileId) {
this.projectId = projectId;
this.resources = resources;
this.privateKeyRawFileId = privateKeyRawFileId;
}
char[] createJwtRsaPassword() {
try {
byte[] privateKeyBytes = decodePrivateKey(resources, privateKeyRawFileId);
return createJwtRsaPassword(projectId, privateKeyBytes).toCharArray();
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Algorithm not supported. (developer error)", e);
} catch (InvalidKeySpecException e) {
throw new IllegalStateException("Invalid Key spec. (developer error)", e);
} catch (IOException e) {
throw new IllegalStateException("Cannot read private key file.", e);
}
}
private static byte[] decodePrivateKey(Resources resources, int privateKeyRawFileId) throws IOException {
try(InputStream inStream = resources.openRawResource(privateKeyRawFileId)) {
return Base64.decode(inputToString(inStream), Base64.DEFAULT);
}
}
private static String inputToString(InputStream is) {
java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
}
private static String createJwtRsaPassword(String projectId, byte[] privateKeyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
return createPassword(projectId, privateKeyBytes, "RSA", SignatureAlgorithm.RS256);
}
private static String createPassword(String projectId, byte[] privateKeyBytes, String algorithmName, SignatureAlgorithm signatureAlgorithm) throws NoSuchAlgorithmException, InvalidKeySpecException {
Instant now = Instant.now();
// Create a JWT to authenticate this device. The device will be disconnected after the token
// expires, and will have to reconnect with a new token. The audience field should always be set
// to the GCP project id.
JwtBuilder jwtBuilder =
Jwts.builder()
.setIssuedAt(Date.from(now))
.setExpiration(Date.from(now.plus(Duration.ofMinutes(20))))
.setAudience(projectId);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(privateKeyBytes);
KeyFactory kf = KeyFactory.getInstance(algorithmName);
return jwtBuilder.signWith(signatureAlgorithm, kf.generatePrivate(spec)).compact();
}
}
MainActivity Class:
package cacaosd.com.sample1;
import android.app.Activity;
import android.hardware.SensorEvent;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.Handler;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import cacaosd.com.sample1.R;
import cacaosd.com.sample1.IotCoreCommunicator;
import com.google.android.things.pio.Gpio;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
public class MainActivity extends Activity {
private IotCoreCommunicator communicator;
private Handler handler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Setup the communication with your Google IoT Core details
communicator = new IotCoreCommunicator.Builder()
.withContext(this)
.withCloudRegion("us-central1") // ex: europe-west1
.withProjectId("my-first-project-198704") // ex: supercoolproject23236
.withRegistryId("vibration") // ex: my-devices
.withDeviceId("my-device") // ex: my-test-raspberry-pi
.withPrivateKeyRawFileId(R.raw.rsa_private)
.build();
HandlerThread thread = new HandlerThread("MyBackgroundThread");
thread.start();
handler = new Handler(thread.getLooper());
handler.post(connectOffTheMainThread); // Use whatever threading mechanism you want
}
private final Runnable connectOffTheMainThread = new Runnable() {
#Override
public void run() {
communicator.connect();
handler.post(sendMqttMessage);
}
};
private final Runnable sendMqttMessage = new Runnable() {
private int i;
/**
* We post 100 messages as an example, 1 a second
*/
#Override
public void run() {
if (i == 100) {
return;
}
// events is the default topic for MQTT communication
String subtopic = "events";
// Your message you want to send
String message = "Hello World " + i++;
communicator.publishMessage(subtopic, message);
handler.postDelayed(this, TimeUnit.SECONDS.toMillis(1));
}
};
#Override
protected void onDestroy() {
communicator.disconnect();
super.onDestroy();
}
}
Update:
I converted the private key from "pem" format to "pkcs8" format by following this documentation and this demo, then the error "Invalid key spec" is gone, but still there is a "FATAL EXCEPTION" and "java.lang.IllegalArgumentException: bad base-64" as shown in the image below:
[![enter image description here][4]][4]
It says that these are the related codes that caused the error (which shown in blue color in the previous image:
IotCorePasswordGenerator.java:47
return Base64.decode(inputToString(inStream), Base64.DEFAULT);
IotCorePasswordGenerator.java:34
byte[] privateKeyBytes = decodePrivateKey(resources, privateKeyRawFileId);
IotCoreCommunicator.java:135
connectOptions.setPassword(passwordGenerator.createJwtRsaPassword());
IotCoreCommunicator.java:101
clientConnect();
MainActivity.java:58
communicator.connect();
Update 2
I deleted the statement "-----BEGIN PRIVATE KEY-----" and the statement "------END PRIVATE KEY-----" and the error "bad base 64" is gone, Now there is another error which is "broken pipe" as shown in image below, when I reopen Android Studio and rebuild the project this error "broken pipe" removed, and when I run the project again it comes back again.
The error (first image)
Private key with start and end statements(second image)
Private key without start and end statements(third image)
From the error, it looks like you registered the device with the wrong type of SSL key. Verify that you created an SSL key that matches the format you specified in IoT Core. I.e. if you created an RSA key with the x509 certificate wrapper, be sure that your device is registered with that type and not just the RSA key.
Also be sure that the private key is actually on the device, and that it's not corrupted.
Edit: Problem may have been the roots.pem wasn't on device to handle the TLS handshake with IoT Core. We shall see... to get it, run: wget https://pki.google.com/roots.pem and put the roots.pem in the same directory as the private key on device.
I strongly recommend that you take a look at the Android Things connector for Cloud IoT core. This project makes it much easier to access Cloud IoT core from Android Things and handles various best practices such as token refresh.
I am writing an App that connects to the Fitbit API correctly and pulls back the data I need. I have an Inner class that extends AsyncTask that lets me complete this. So for example, my MainActivity.java opens the Fitbit OAuth2 page and the user logs in. The user is then directed back to the UserActivity.java and their info is displayed.
I now want to add another Activity that pulls back the information for the Activities that they carried out. So, my question is, do I need to add another inner class in my ActivitiesActivity.java or is there some other way to get the data. I know people have used an Interface before but I'm not sure how they work with AsyncTask.
package com.jordan.fitbit_connect;
import android.net.Uri;
import android.os.Bundle;
import android.support.customtabs.CustomTabsIntent;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
String response_type = "token";
String client_id = "22CJH3";
String redirect_uri = "myapplication://login";
String scope = "activity%20nutrition%20heartrate%20location%20nutrition%20profile%20settings%20sleep%20social%20weight";
String url = "https://www.fitbit.com/oauth2/authorize?" + "response_type=" + response_type + "&client_id=" + client_id + "&redirect_uri=" + redirect_uri + "&scope=" + scope;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//
// CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder().build();
// customTabsIntent.launchUrl(this, Uri.parse(url));
connectToFitbit();
}
public void connectToFitbit()
{
Button btn = (Button)findViewById(R.id.btnConnect);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder().build();
customTabsIntent.launchUrl(getApplicationContext(), Uri.parse(url));
}
});
}
}
package com.jordan.fitbit_connect;
import android.content.Intent;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class TestActivity extends AppCompatActivity
{
//String to hold the data sent back by the Intent
String string;
//String to extract the token from 'string' above
private static String token;
//Strings to get the data from the JSON Object
public static String name, avatar, age, weight, height;
TextView username, txtAge, txtWeight, txtHeight, txtBMI;
float bmi;
ImageView imgViewAvatar;
//-------------------------------------- START onNewIntent()------------------------------------
/*
This method returns the URI from the Intent as an encoded String
*/
#Override
protected void onNewIntent(Intent intent)
{
string = intent.getDataString();
}
//-------------------------------------- END onNewIntent()--------------------------------------
//-------------------------------------- START onCreate()---------------------------------------
/*
Default method when the class is created
*/
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
onNewIntent(getIntent());
token = string.substring(string.indexOf("&access_token")+36,308);
Log.i("TAG", "Access Token: "+ token);
Log.i("TAG", "Data String: " + string);
//new JSONTask().execute("https://api.fitbit.com/1.2/user/-/sleep/date/2017-10-26.json");
//new JSONTask().execute("https://api.fitbit.com/1/user/-/activities/steps/date/today/6m.json");
new JSONTask().execute("https://api.fitbit.com/1/user/-/profile.json");
}
//-------------------------------------- END onCreate()-----------------------------------------
//-------------------------------------- START of inner class JSONTask -------------------------
public class JSONTask extends AsyncTask<String,String,String>
{
#Override
protected void onPreExecute() {
super.onPreExecute();
username = (TextView)findViewById(R.id.txtUser);
imgViewAvatar = (ImageView)findViewById(R.id.imgViewAvatar);
txtAge = (TextView)findViewById(R.id.txtAge);
txtWeight = (TextView) findViewById(R.id.txtWeight);
txtHeight = (TextView) findViewById(R.id.txtHeight);
txtBMI = (TextView) findViewById(R.id.txtBMI);
}
//-------------------------------------- START doInBackground()-----------------------------
/*
This method is what happens on the background thread when the
app is running. It will
*/
#Override
protected String doInBackground(String... params)
{
HttpURLConnection connection = null;
BufferedReader reader = null;
try
{
URL url = new URL(params[0]);
connection = (HttpURLConnection)url.openConnection();
connection.setRequestMethod("GET");
connection.setDoOutput(false);
connection.addRequestProperty("Authorization", "Bearer " + token);
connection.connect();
InputStream stream = (InputStream)connection.getInputStream();
reader = new BufferedReader(new InputStreamReader(stream));
StringBuffer buffer = new StringBuffer();
String line = "";
while((line = reader.readLine()) !=null)
{
buffer.append(line);
}
return buffer.toString();
} catch (MalformedURLException e)
{
e.printStackTrace();
} catch (IOException e)
{
e.toString();
}
return null;
}
//-------------------------------------- END doInBackground()-------------------------------
//-------------------------------------- START onPostExecute()------------------------------
#Override
protected void onPostExecute(String data)
{
super.onPostExecute(data);
Log.i("TAG", data);
try
{
//GET ALL THE JSON DATA
JSONObject allData = new JSONObject(data);
//GET THE USERNAME
JSONObject userObject = allData.getJSONObject("user");
name = userObject.getString("fullName");
username.append(" "+name);
//GET THE USER'S AVATAR
avatar = userObject.getString("avatar640");
Picasso.get().load(avatar).into(imgViewAvatar);
//GET THE USER'S AGE
age = userObject.getString("age");
txtAge.append(" "+age);
weight = userObject.getString("weight");
txtWeight.append(" "+weight);
float weightFloat = Float.parseFloat(weight);
height = userObject.getString("height");
txtHeight.append(" "+height);
float heightFloat= Float.parseFloat(height)/100;
bmi = (float)(weightFloat/(heightFloat * heightFloat));
if(bmi <= 16)
{
txtBMI.setTextColor(Color.YELLOW);
txtBMI.append(" "+ String.valueOf(bmi) + " - You are severely underweight!");
}
else if(bmi <= 18.5)
{
txtBMI.setTextColor(Color.GRAY);
txtBMI.append(" "+ String.valueOf(bmi) + " - You are underweight!");
}
else if(bmi <= 25)
{
txtBMI.setTextColor(Color.GREEN);
txtBMI.append(" "+ String.valueOf(bmi) + " - Your weight is normal");
}
else if(bmi <= 30)
{
txtBMI.setTextColor(Color.parseColor("#FFA500"));
txtBMI.append(" "+ String.valueOf(bmi) + " - You are overweight!");
}
else
{
txtBMI.setTextColor(Color.RED);
txtBMI.append(" " + String.valueOf(bmi) + " - You are obese!");
}
// for(int i =0; i< userObject.length(); i++) {
//3.DECLARE ANOTHER JSONOBJECT THAT EXTRACTS THE OBECT FROM THE SPECIFIED ARRAY
//JSONObject sleep = sleepArray.getJSONObject(i);
//4.Then use a getString to get the data from the object
//name = userObject.getString("firstName");
// Log.i("TAG",name);
}
catch (JSONException e)
{
e.printStackTrace();
}
}
}
//-------------------------------------- END of inner class JSONTask ---------------------------
}
One of the methods using AsynTask in different Activities, creating a callback interface.
Create a callback interface
interface AsyncTaskListener<T> {
public void onComplete(T result);
}
Then in your MainActivity and TestActivity:
public class MainActivity extends AppCompatActivity
implements AsyncTaskListener<String> {
public void onComplete(String result) {
// your staff here
}
}
public class TestActivity extends AppCompatActivity
implements AsyncTaskListener<String> {
public void onComplete(String result) {
// your staff here
}
}
And add to your AsyncTask class:
public class JSONTask extends AsyncTask<String, String, String>
private AsyncTaskListener<String> listener;
public JSONTask (AsyncTaskListener<String> callback) {
this.listener = callback;
}
protected void onPostExecute(String result) {
listener.onComplete(result); // calling onComplate interface
}
After an exhaustive search on the internet, I managed to assemble a code that sends data to a server socket created in VB.NET.
' Here is my VB.NET code
Imports System.Net.Sockets
Imports System.Text
Imports System.Net
Public Module MainModule
Private TcpListener As New TcpListener(IPAddress.Parse("10.0.0.100"), 11000)
Dim TcpClient As New TcpClient
Dim NetworkStream As NetworkStream
Public Sub Main()
TcpListener.Start()
While (True)
TcpClient = TcpListener.AcceptTcpClient()
NetworkStream = TcpClient.GetStream()
Dim r_byt(TcpClient.ReceiveBufferSize) As Byte
Dim r_byt_size As Integer = NetworkStream.Read(r_byt, 0, TcpClient.ReceiveBufferSize) - 1
Dim data As String = Encoding.ASCII.GetString(r_byt, 0, r_byt_size)
Console.WriteLine(data)
Dim s_byt() As Byte = Encoding.ASCII.GetBytes("testing")
NetworkStream.Write(s_byt, 0, s_byt.Length)
NetworkStream.Flush()
NetworkStream.Close()
TcpClient.Close()
End While
TcpListener.Stop()
End Sub
End Module
// Here is my Android code
package com.javacodegeeks.android.androidsocketclient;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class Client extends Activity {
private static final String SERVER_IP = "10.0.0.100";
private static final int SERVER_PORT = 11000;
private Socket socket;
private EditText InputText = null;
private Button ButtonSend = null;
private TextView LabelReceived = null;
private Thread thread = null;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
InputText = (EditText)findViewById(R.id.InputText);
ButtonSend = (Button)findViewById(R.id.ButtonSend);
LabelReceived = (TextView)findViewById(R.id.LabelReceived);
thread = new Thread(new ClientThread());
if (thread != null) {
thread.start();
}
ButtonSend.setOnClickListener(new OnClickListener() {
public void onClick(View arg0) {
try {
if (socket != null) {
PrintWriter out = new PrintWriter(socket.getOutputStream());
out.println(InputText.getText().toString());
out.flush();
out.close();
} else {
LabelReceived.setText("socket is null!");
}
} catch (Exception e) {
LabelReceived.setText(e.getMessage());
}
}
});
}
class ClientThread implements Runnable {
public void run() {
try {
InetAddress IAddress = InetAddress.getByName(SERVER_IP);
socket = new Socket(IAddress, SERVER_PORT);
} catch (Exception e) {
socket = null;
}
}
}
}
The two are working, when I press the "send button" it goes to the server, but when I try to do it again nothing is received.
I would like to add a webserver to my android application for uploading small files to the phone.
The user would start the webserver from the phone by hitting a button. He would then see an ip address that can be accessed by any browser from a pc. The website behind this ip address should show a file upload opportunity.
My question is: Is there an open source project similar to my needs? Or how would you recommend doing this?
you can use NanoHttpd link it's very weight android web server that is nicely embbedible..
package .....;
import java.io.IOException;
import java.util.Map.Entry;
import java.util.Properties;
import android.app.Activity;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.widget.TextView;
public class AndroidWebServerActivity extends Activity {
private static final int PORT = 8765;
private TextView hello;
private MyHTTPD server;
private Handler handler = new Handler();
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
#Override
protected void onResume() {
super.onResume();
try {
server = new MyHTTPD();
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
protected void onPause() {
super.onPause();
if (server != null)
server.stop();
}
private class MyHTTPD extends NanoHTTPD {
public MyHTTPD() throws IOException {
super(PORT, null);
}
#Override
public Response serve(String uri, String method, Properties header, Properties parms, Properties files) {
final StringBuilder buf = new StringBuilder();
for (Entry<Object, Object> kv : header.entrySet())
buf.append(kv.getKey() + " : " + kv.getValue() + "\n");
handler.post(new Runnable() {
#Override
public void run() {
}
});
final String html = "<html><head><head><body><h1>Hello, World</h1></body></html>";
return new NanoHTTPD.Response(HTTP_OK, MIME_HTML, html);
}
}
}
I am trying to use NanoHTTP to serve up an HTML file. However, NanoHTTP is relatively un-documented, and I am new to Android. My question is, where do I store the html file, and how specifically can I serve it up using NanoHTTP.
A late answer but may be useful to others.
Here is a simple hello Web Server, not exactly what you ask, but you can continue from here. The following program supposes you have a www directory in the root of the SD Card and a file index.html inside.
Main activity Httpd.java:
package com.inforscience.web;
import android.app.Activity;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import java.io.*;
import java.util.*;
public class Httpd extends Activity
{
private WebServer server;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
server = new WebServer();
try {
server.start();
} catch(IOException ioe) {
Log.w("Httpd", "The server could not start.");
}
Log.w("Httpd", "Web server initialized.");
}
// DON'T FORGET to stop the server
#Override
public void onDestroy()
{
super.onDestroy();
if (server != null)
server.stop();
}
private class WebServer extends NanoHTTPD {
public WebServer()
{
super(8080);
}
#Override
public Response serve(String uri, Method method,
Map<String, String> header,
Map<String, String> parameters,
Map<String, String> files) {
String answer = "";
try {
// Open file from SD Card
File root = Environment.getExternalStorageDirectory();
FileReader index = new FileReader(root.getAbsolutePath() +
"/www/index.html");
BufferedReader reader = new BufferedReader(index);
String line = "";
while ((line = reader.readLine()) != null) {
answer += line;
}
reader.close();
} catch(IOException ioe) {
Log.w("Httpd", ioe.toString());
}
return new NanoHTTPD.Response(answer);
}
}
}
Obviously the NanoHTTPD class must be in the same package.
You need to grant internet permission in AndroidManifest.xml.
<uses-permission android:name="android.permission.INTERNET" />
and read external storage permission.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
EDIT: To access the server open you web browser with the IP of your device, e.g. 192.168.1.20:8080.
NOTES:
Tested in Android 2.3
The use of port 80 is restricted to the root user(http://www.mail-archive.com/android-developers#googlegroups.com/msg47377.html).
Pretty good source code can be found here:
https://github.com/Teaonly/android-eye
Chceck assets folder where html and JavaScript files are stored
https://github.com/Teaonly/android-eye/tree/master/assets
TeaServer - server implementation
https://github.com/Teaonly/android-eye/blob/master/src/teaonly/droideye/TeaServer.java
MainActivity - server initialization
https://github.com/Teaonly/android-eye/blob/master/src/teaonly/droideye/MainActivity.java
Updated WebServer class (see rendon's reply) that works with current NanoHTTPD version:
private class WebServer extends NanoHTTPD {
public WebServer() {
super(8080);
}
#Override
public Response serve(IHTTPSession session) {
String answer = "";
try {
// Open file from SD Card
File root = Environment.getExternalStorageDirectory();
FileReader index = new FileReader(root.getAbsolutePath() +
"/www/index.html");
BufferedReader reader = new BufferedReader(index);
String line = "";
while ((line = reader.readLine()) != null) {
answer += line;
}
reader.close();
} catch(IOException ioe) {
Log.w("Httpd", ioe.toString());
}
return newFixedLengthResponse(answer);
}
}
Take a look at how I serve HTML files and other type of files too.
I have a AndroidWebServer class which extends the Nanohttpd class.
This is my response method -->
public Response serve(IHTTPSession session) {
String uri=session.getUri();
String msg = "<html><body><h1>Hello server</h1>\n";
File [] arrayfile;
int i=0;
try{
session.parseBody(new HashMap<String, String>());
}catch (ResponseException | IOException r){
r.printStackTrace();
}
Map<String, String> parms = session.getParms();
if (parms.get("username") == null) {
msg += "<form action='?' method='get'>\n <p>Your name: <input type='text' name='username'></p>\n" + "</form>\n";
} else {
msg += "<p>Hello, " + parms.get("username") + "!</p>";
}
msg += "<br><br><a href='/Open_rap'>Open Image of Lionel Messi</a><br><br>";
msg += "<br><br><a href='/files'>Browse Files</a><br><br>";
msg += "<br><br><a href='/getmethod'>GET METHOD OPERATION</a><br><br>";
msg += "<br><br><a href='/postmethod'>POST METHOD OPERATION</a><br><br>";
msg += "<br><br><a href='/jquery'>JQUERY OPERATION</a><br><br>";
if(uri.equals("/hello")){
String response="Hello World";
return newFixedLengthResponse(response);
}
else if(uri.equals("/getmethod")){
String html="<html><head><h1>Welcome to the Form</h1><head/><body>";
if(parms.get("name")==null){
html +="<form action='' method='get'> \n " +
"<p>Enter Your Name:</p> <input type='text' name='name'>" +
"</form>" +
"</body>";
}
else{
html +="<p>Hello Mr. "+ parms.get("name") +"</p>"+
"</body> ";
}
html +="</html>";
return newFixedLengthResponse(html);
}
else if(uri.equals("/postmethod")){
String html="<html><head><h1>Welcome to the Form</h1><head/><body>";
Map<String, String> files = new HashMap<String, String>();
Method method = session.getMethod();
String postParameter="";
html +="<form action='' method='post'> \n " +
"<p>Enter Your Name:</p> <input type='text' name='name'>" +
"</form>";
if (Method.POST.equals(method) || Method.PUT.equals(method)) {
try {
session.parseBody(files);
} catch (IOException ioe) {
try {
// return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
} catch (Exception e) {
e.printStackTrace();
Log.d("Exception", e.getMessage());
}
} catch (ResponseException re) {
try {
// return newFixedLengthResponse(re.getStatus(), MIME_PLAINTEXT, re.getMessage());
} catch (Exception e) {
e.printStackTrace();
Log.d("Exception", re.getMessage());
}
}
}
html +="</body></html>";
String postBody = session.getQueryParameterString();
postParameter = session.getParms().get("name");
Log.d("Postbody",postBody+"\n"+postParameter);
if(postParameter!=null){
String html1="<html><head><h1>"+ postParameter +"</h1><head></html>";
return newFixedLengthResponse(html1);
}
return newFixedLengthResponse(Response.Status.OK,"text/html",html);
}
else if(uri.equals("/Open_rap")){
File root= Environment.getExternalStorageDirectory();
FileInputStream fis = null;
File file = new File(root.getAbsolutePath() + "/www/messi.jpg");
Log.d("Path",root.getAbsolutePath());
try{
if(file.exists())
{
fis = new FileInputStream(file);
}
else
Log.d("FOF :", "File Not exists:");
}catch (FileNotFoundException e)
{
e.printStackTrace();
}
return newFixedLengthResponse(Response.Status.OK,"image/jpeg",fis, file.length() );
}
else {
return newFixedLengthResponse(msg + "</body></html>\n");
}
}
As you can see, I have implemented GET and POST method. You can find each in this part of the code uri.equals("/getmethod") and uri.equals("/getmethod").
Also, you can see the part --> uri.equals("/openrap") , here I am serving a JPG file to the client's browser and the image is present in the internal directory at /www/ folder.
Ping me if you have any doubts.
Try this...
Create 2 packages(activity, util), only for organization
In activity create the class MainActivity.java in util create the class AndroidWebServer.java
package awserverfatepi.com.activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.wifi.WifiManager;
import android.support.design.widget.CoordinatorLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import awserverfatepi.com.R;
import awserverfatepi.com.util.AndroidWebServer;
public class MainActivity extends AppCompatActivity {
private static final int DEFAULT_PORT = 8080;
private AndroidWebServer androidWebServer;
private BroadcastReceiver broadcastReceiverNetworkState;
private static boolean isStarted = false;
private CoordinatorLayout coordinatorLayout;
private EditText editTextPort;
private FloatingActionButton floatingActionButtonOnOff;
private View textViewMessage;
private TextView textViewIpAccess;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initGui();
setIpAccess();
floatingActionButtonOnOff = (FloatingActionButton) findViewById(R.id.floatingActionButtonOnOff);
floatingActionButtonOnOff.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (isConnectedInWifi()) {
if (!isStarted && startAndroidWebServer()) {
isStarted = true;
textViewMessage.setVisibility(View.VISIBLE);
floatingActionButtonOnOff.setBackgroundTintList(ContextCompat.getColorStateList(MainActivity.this,
R.color.colorGreen));
editTextPort.setEnabled(false);
} else if (stopAndroidWebServer()) {
isStarted = false;
textViewMessage.setVisibility(View.INVISIBLE);
floatingActionButtonOnOff.setBackgroundTintList(ContextCompat.getColorStateList(MainActivity.this,
R.color.colorRed));
editTextPort.setEnabled(true);
}
} else {
Snackbar.make(coordinatorLayout, getString(R.string.wifi_message), Snackbar.LENGTH_LONG).show();
}
}
});
initBroadcastReceiverNetworkStateChanged();
}
private void initGui() {
coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinatorLayout);
editTextPort = (EditText) findViewById(R.id.editTextPort);
textViewMessage = findViewById(R.id.textViewMessage);
textViewIpAccess = (TextView) findViewById(R.id.textViewIpAccess);
}
private boolean startAndroidWebServer() {
if (!isStarted) {
int port = getPortFromEditText();
try {
if (port == 0) {
throw new Exception();
}
androidWebServer = new AndroidWebServer(port);
androidWebServer.start();
return true;
} catch (Exception e) {
e.printStackTrace();
Snackbar.make(coordinatorLayout, "A porta " + port + " não está funcionando, por favor altere para outra no intervalo" +
" entre 1000 e 9999.", Snackbar.LENGTH_LONG).show();
}
}
return false;
}
private boolean stopAndroidWebServer() {
if (isStarted && androidWebServer != null) {
androidWebServer.stop();
return true;
}
return false;
}
private void setIpAccess() {
textViewIpAccess.setText(getIpAccess());
}
private void initBroadcastReceiverNetworkStateChanged() {
final IntentFilter filters = new IntentFilter();
filters.addAction("android.net.wifi.WIFI_STATE_CHANGED");
filters.addAction("android.net.wifi.STATE_CHANGE");
broadcastReceiverNetworkState = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
setIpAccess();
}
};
super.registerReceiver(broadcastReceiverNetworkState, filters);
}
private String getIpAccess() {
WifiManager wifiManager = (WifiManager) getSystemService(WIFI_SERVICE);
int ipAddress = wifiManager.getConnectionInfo().getIpAddress();
final String formatedIpAddress = String.format("%d.%d.%d.%d", (ipAddress & 0xff), (ipAddress >> 8 & 0xff),
(ipAddress >> 16 & 0xff), (ipAddress >> 24 & 0xff));
return "http://" + formatedIpAddress + ":";
}
private int getPortFromEditText() {
String valueEditText = editTextPort.getText().toString();
return (valueEditText.length() > 0) ? Integer.parseInt(valueEditText) : DEFAULT_PORT;
}
public boolean isConnectedInWifi() {
WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
NetworkInfo networkInfo = ((ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isAvailable() && networkInfo.isConnected()
&& wifiManager.isWifiEnabled() && networkInfo.getTypeName().equals("WIFI")) {
return true;
}
return false;
}
public boolean onKeyDown(int keyCode, KeyEvent evt) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (isStarted) {
new AlertDialog.Builder(this)
.setTitle(R.string.warning)
.setMessage(R.string.dialog_exit_message)
.setPositiveButton(getResources().getString(android.R.string.ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
finish();
}
})
.setNegativeButton(getResources().getString(android.R.string.cancel), null)
.show();
} else {
finish();
}
return true;
}
return false;
}
#Override
protected void onDestroy() {
super.onDestroy();
stopAndroidWebServer();
isStarted = false;
if (broadcastReceiverNetworkState != null) {
unregisterReceiver(broadcastReceiverNetworkState);
}
}
}
In the AndroidWebserver.java
package awserverfatepi.com.util;
import java.util.Map;
import fi.iki.elonen.NanoHTTPD;
public class AndroidWebServer extends NanoHTTPD {
public AndroidWebServer(int port) {
super(port);
}
public AndroidWebServer(String hostname, int port) {
super(hostname, port);
}
#Override
public Response serve(IHTTPSession session) {
String msg = "<html><body><h1>Hello World</h1>\n";
Map<String, String> parms = session.getParms();
if (parms.get("username") == null) {
msg += "<form action='?' method='get'>\n <p>Seu nome: <input type='text' name='username'></p>\n" + "</form>\n";
} else {
msg += "<p>Hello, " + parms.get("username") + "!</p>";
}
return newFixedLengthResponse( msg + "</body></html>\n" );
}
}
Don't forget Manifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
And last
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="150dp"
android:scaleType="centerCrop"
android:src="#drawable/header" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#color/colorPrimaryLight"
android:gravity="center"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="#+id/textViewIpAccess"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="http://000.000.000.000:"
android:textColor="#android:color/white"
android:textSize="20sp"
android:textStyle="bold" />
<EditText
android:id="#+id/editTextPort"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:gravity="center"
android:hint="8080"
android:inputType="numberDecimal"
android:maxLength="4"
android:text="8080"
android:textColor="#android:color/white"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:id="#+id/textViewMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="50dp"
android:layout_marginRight="50dp"
android:layout_marginTop="50dp"
android:gravity="center"
android:text="#string/message"
android:textColor="#android:color/white"
android:textSize="18sp"
android:visibility="invisible" />
</LinearLayout>
</LinearLayout>
<android.support.design.widget.FloatingActionButton
android:id="#+id/floatingActionButtonOnOff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:elevation="4dp"
android:src="#drawable/on_btn"
app:backgroundTint="#color/colorRed" />
</android.support.design.widget.CoordinatorLayout>
One more example based on previous posts with AssetManager, MimeTypes and routing.
This static server will work for React apps. Your can run React app by using Andorid WebView. Create main/assets/index.html or copy React build folder into main/assets/ directory. Add url http://localhost:8080 to your WebView or browser.
package com.example.httpd;
import androidx.appcompat.app.AppCompatActivity;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.util.Log;
import java.io.*;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.util.Random;
import fi.iki.elonen.NanoHTTPD;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private HttpServer server;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
server = new HttpServer();
try {
server.start();
} catch (IOException ioe) {
Log.d(TAG, "The server could not start.");
}
Log.d(TAG, "Web server initialized.");
}
// DON'T FORGET to stop the server
#Override
public void onDestroy() {
super.onDestroy();
if (server != null)
server.stop();
}
private class HttpServer extends NanoHTTPD {
public static final String
MIME_PLAINTEXT = "text/plain",
MIME_HTML = "text/html",
MIME_JS = "application/javascript",
MIME_CSS = "text/css",
MIME_PNG = "image/png",
MIME_DEFAULT_BINARY = "application/octet-stream",
MIME_XML = "text/xml";
private static final int PORT = 8080;
private static final String TAG = "HttpServer";
private AssetManager assetManager;
public HttpServer() {
super(PORT);
}
#Override
public Response serve(IHTTPSession session) {
assetManager = getAssets();
InputStream inputStream;
Response response = newChunkedResponse(Response.Status.BAD_REQUEST, MIME_PLAINTEXT, null);
String uri = session.getUri();
try {
if (session.getMethod() == Method.GET && uri != null) {
if (uri.contains(".js")) {
inputStream = assetManager.open(uri.substring(1));
return newChunkedResponse(Response.Status.OK, MIME_JS, inputStream);
} else if (uri.contains(".css")) {
inputStream = assetManager.open(uri.substring(1));
return newChunkedResponse(Response.Status.OK, MIME_CSS, inputStream);
} else if (uri.contains(".png")) {
inputStream = assetManager.open(uri.substring(1));
return newChunkedResponse(Response.Status.OK, MIME_PNG, inputStream);
} else if (uri.contains("/mnt/sdcard")) {
Log.d(TAG, "request for media on sdCard " + uri);
File file = new File(uri);
FileInputStream fileInputStream = new FileInputStream(file);
FileNameMap fileNameMap = URLConnection.getFileNameMap();
String contentType = fileNameMap.getContentTypeFor(uri);
Response streamResponse = newChunkedResponse(Response.Status.OK, contentType, fileInputStream);
Random random = new Random();
String hexString = Integer.toHexString(random.nextInt());
streamResponse.addHeader("ETag", hexString);
streamResponse.addHeader("Connection", "Keep-alive");
return streamResponse;
} else {
inputStream = assetManager.open("index.html");
return newChunkedResponse(Response.Status.OK, MIME_HTML, inputStream);
}
}
} catch (IOException e) {
Log.d(TAG, e.toString());
}
return response;
}
}
}
build.gradle
dependencies {
...
implementation 'org.nanohttpd:nanohttpd:2.3.1'
...
}
res/values/strings.html
<resources>
<string name="http_index">index.html</string>
</resources>
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Have a nice day! :)