Do not depend on Qt at all on Android

Do the necessary modifications to not depend on Qt to run on Android
  both in libretroshare and in retroshare-service
This commit is contained in:
Gioacchino Mazzurco 2021-11-30 11:03:22 +01:00
parent 941ec42502
commit 856ce2ffb1
No known key found for this signature in database
GPG key ID: A1FBCA3872E87051
36 changed files with 1094 additions and 352 deletions

View file

@ -0,0 +1,75 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef LOCAL_ARRAY_H_included
#define LOCAL_ARRAY_H_included
#include <cstddef>
#include <new>
/**
* A fixed-size array with a size hint. That number of bytes will be allocated
* on the stack, and used if possible, but if more bytes are requested at
* construction time, a buffer will be allocated on the heap (and deallocated
* by the destructor).
*
* The API is intended to be a compatible subset of C++0x's std::array.
*/
template <size_t STACK_BYTE_COUNT>
class LocalArray {
public:
/**
* Allocates a new fixed-size array of the given size. If this size is
* less than or equal to the template parameter STACK_BYTE_COUNT, an
* internal on-stack buffer will be used. Otherwise a heap buffer will
* be allocated.
*/
LocalArray(size_t desiredByteCount) : mSize(desiredByteCount) {
if (desiredByteCount > STACK_BYTE_COUNT) {
mPtr = new char[mSize];
} else {
mPtr = &mOnStackBuffer[0];
}
}
/**
* Frees the heap-allocated buffer, if there was one.
*/
~LocalArray() {
if (mPtr != &mOnStackBuffer[0]) {
delete[] mPtr;
}
}
// Capacity.
size_t size() { return mSize; }
bool empty() { return mSize == 0; }
// Element access.
char& operator[](size_t n) { return mPtr[n]; }
const char& operator[](size_t n) const { return mPtr[n]; }
private:
char mOnStackBuffer[STACK_BYTE_COUNT];
char* mPtr;
size_t mSize;
// Disallow copy and assignment.
LocalArray(const LocalArray&);
void operator=(const LocalArray&);
};
#endif // LOCAL_ARRAY_H_included

View file

@ -0,0 +1,49 @@
= README Android ifaddrs
Android API level < 24 doesn't provide `getifaddrs` and related functions, we
have tested multiple ways to overcome this issue.
== Non Weorking alternative implementations
https://github.com/kmackay/android-ifaddrs
https://github.com/morristech/android-ifaddrs
https://www.openhub.net/p/android-ifaddrs/
Compiles but then segfault at runtime.
== Qt implementation
Using `QNetworkInterface::allAddresses()` provided by Qt works but at time of
writing (Q4 2021) on newer Android the log is flooded by warnings as we reported
here
https://bugreports.qt.io/browse/QTBUG-78659
plus depending on Qt networking module just for this is frustrating.
Update: the warning flood seems have been fixed in later Qt versions
https://bugreports.qt.io/browse/QTBUG-86394
This solution was the first working we implemented in our code it has been
removed to avoid dependency on Qt, as lighter alternatives are possible.
== Code copied from Android Gingerbread release
As explained here
https://stackoverflow.com/a/57112520
even older Android have `getifaddrs` implementations but doesn't make them
accessible in the API, in particular the one included in Android Gingerbread
https://android.googlesource.com/platform/libcore/+/refs/heads/gingerbread-release/luni/src/main/native/ifaddrs-android.h
https://android.googlesource.com/platform/libcore/+/refs/heads/gingerbread-release/
is particularly easy to include in our code base and compile.
This solution seems the best fitting and doesn't introduce dependency on Qt.
Newer Android releases (expecially 11) have introduced multiple restrictions
on network information access so we suggest you to prepare different APK for
different API level in order to use the `getifaddrs` provided by Android NDK
which deal gracefully with those restrictions as soon as available.

View file

@ -0,0 +1,46 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef SCOPED_FD_H_included
#define SCOPED_FD_H_included
#include <unistd.h>
// A smart pointer that closes the given fd on going out of scope.
// Use this when the fd is incidental to the purpose of your function,
// but needs to be cleaned up on exit.
class ScopedFd {
public:
explicit ScopedFd(int fd) : fd(fd) {
}
~ScopedFd() {
close(fd);
}
int get() const {
return fd;
}
private:
int fd;
// Disallow copy and assignment.
ScopedFd(const ScopedFd&);
void operator=(const ScopedFd&);
};
#endif // SCOPED_FD_H_included

View file

@ -0,0 +1,125 @@
/*******************************************************************************
* *
* libretroshare: retroshare core library *
* *
* Copyright (C) 2016-2021 Gioacchino Mazzurco <gio@eigenlab.org> *
* Copyright (C) 2021 Asociación Civil Altermundi <info@altermundi.net> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#pragma once
// Inspired by: https://codelab.wordpress.com/2014/11/03/how-to-use-standard-output-streams-for-logging-in-android-apps/
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <fcntl.h> // for O_NONBLOCK
#include <android/log.h>
#include <atomic>
#include <string>
/**
* On Android stdout and stderr of native code is discarded, instancing this
* class at the beginning of the main of your program to get them (stdout and
* stderr) on logcat output.
*/
class AndroidCoutCerrCatcher
{
public:
AndroidCoutCerrCatcher(const std::string& dTag = "RetroShare",
android_LogPriority stdout_pri = ANDROID_LOG_INFO,
android_LogPriority stderr_pri = ANDROID_LOG_ERROR) :
tag(dTag), cout_pri(stdout_pri), cerr_pri(stderr_pri), should_stop(false)
{
// make stdout line-buffered
//setvbuf(stdout, 0, _IOLBF, 0);
// make stdout and stderr unbuffered
setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stderr, 0, _IONBF, 0);
// create the pipes and redirect stdout and stderr
pipe2(pout_fd, O_NONBLOCK);
dup2(pout_fd[1], STDOUT_FILENO);
pipe2(perr_fd, O_NONBLOCK);
dup2(perr_fd[1], STDERR_FILENO);
// spawn the logging thread
pthread_create(&thr, 0, thread_func, this);
pthread_detach(thr);
}
~AndroidCoutCerrCatcher()
{
should_stop = true;
pthread_join(thr, nullptr);
}
private:
const std::string tag;
const android_LogPriority cout_pri;
const android_LogPriority cerr_pri;
int pout_fd[2];
int perr_fd[2];
pthread_t thr;
std::atomic<bool> should_stop;
static void* thread_func(void* instance)
{
__android_log_write(
ANDROID_LOG_INFO, "RetroShare",
"Android standard I/O catcher start" );
AndroidCoutCerrCatcher &i = *static_cast<AndroidCoutCerrCatcher*>(instance);
std::string out_buf;
std::string err_buf;
while (!i.should_stop)
{
for(char c; read(i.pout_fd[0], &c, 1) == 1;)
{
out_buf += c;
if(c == '\n')
{
__android_log_write(i.cout_pri, i.tag.c_str(), out_buf.c_str());
out_buf.clear();
}
}
for(char c; read(i.perr_fd[0], &c, 1) == 1;)
{
err_buf += c;
if(c == '\n')
{
__android_log_write(i.cerr_pri, i.tag.c_str(), err_buf.c_str());
err_buf.clear();
}
}
usleep(10000);
}
__android_log_write(
ANDROID_LOG_INFO, "RetroShare",
"Android standard I/O catcher stop" );
return nullptr;
}
};

View file

@ -0,0 +1,42 @@
/*******************************************************************************
* *
* libretroshare: retroshare core library *
* *
* Copyright (C) 2021 Gioacchino Mazzurco <gio@eigenlab.org> *
* Copyright (C) 2021 Asociación Civil Altermundi <info@altermundi.net> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#include "rs_android/rsjni.hpp"
namespace jni
{
Local<Object<RsJni::ErrorConditionWrap>> MakeAnything(
ThingToMake<RsJni::ErrorConditionWrap>, JNIEnv& env,
const std::error_condition& ec )
{
auto& clazz = jni::Class<RsJni::ErrorConditionWrap>::Singleton(env);
static auto method =
clazz.GetConstructor<jni::jint, jni::String, jni::String>(env);
jni::jint value = ec.value();
auto message = jni::Make<jni::String>(env, ec.message());
auto category = jni::Make<jni::String>(env, ec.category().name());
return clazz.New(env, method, value, message, category);
}
}

View file

@ -0,0 +1,228 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef IFADDRS_ANDROID_H_included
#define IFADDRS_ANDROID_H_included
#include <arpa/inet.h>
#include <cstring>
#include <errno.h>
#include <net/if.h>
#include <netinet/in.h>
#include <new>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include "LocalArray.h"
#include "ScopedFd.h"
// Android (bionic) doesn't have getifaddrs(3)/freeifaddrs(3).
// We fake it here, so java_net_NetworkInterface.cpp can use that API
// with all the non-portable code being in this file.
// Source-compatible subset of the BSD struct.
struct ifaddrs {
// Pointer to next struct in list, or NULL at end.
ifaddrs* ifa_next;
// Interface name.
char* ifa_name;
// Interface flags.
unsigned int ifa_flags;
// Interface network address.
sockaddr* ifa_addr;
// Interface netmask.
sockaddr* ifa_netmask;
ifaddrs(ifaddrs* next)
: ifa_next(next), ifa_name(NULL), ifa_flags(0), ifa_addr(NULL), ifa_netmask(NULL)
{
}
~ifaddrs() {
delete ifa_next;
delete[] ifa_name;
delete ifa_addr;
delete ifa_netmask;
}
// Sadly, we can't keep the interface index for portability with BSD.
// We'll have to keep the name instead, and re-query the index when
// we need it later.
bool setNameAndFlagsByIndex(int interfaceIndex) {
// Get the name.
char buf[IFNAMSIZ];
char* name = if_indextoname(interfaceIndex, buf);
if (name == NULL) {
return false;
}
ifa_name = new char[strlen(name) + 1];
strcpy(ifa_name, name);
// Get the flags.
ScopedFd fd(socket(AF_INET, SOCK_DGRAM, 0));
if (fd.get() == -1) {
return false;
}
ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strcpy(ifr.ifr_name, name);
int rc = ioctl(fd.get(), SIOCGIFFLAGS, &ifr);
if (rc == -1) {
return false;
}
ifa_flags = ifr.ifr_flags;
return true;
}
// Netlink gives us the address family in the header, and the
// sockaddr_in or sockaddr_in6 bytes as the payload. We need to
// stitch the two bits together into the sockaddr that's part of
// our portable interface.
void setAddress(int family, void* data, size_t byteCount) {
// Set the address proper...
sockaddr_storage* ss = new sockaddr_storage;
memset(ss, 0, sizeof(*ss));
ifa_addr = reinterpret_cast<sockaddr*>(ss);
ss->ss_family = family;
uint8_t* dst = sockaddrBytes(family, ss);
memcpy(dst, data, byteCount);
}
// Netlink gives us the prefix length as a bit count. We need to turn
// that into a BSD-compatible netmask represented by a sockaddr*.
void setNetmask(int family, size_t prefixLength) {
// ...and work out the netmask from the prefix length.
sockaddr_storage* ss = new sockaddr_storage;
memset(ss, 0, sizeof(*ss));
ifa_netmask = reinterpret_cast<sockaddr*>(ss);
ss->ss_family = family;
uint8_t* dst = sockaddrBytes(family, ss);
memset(dst, 0xff, prefixLength / 8);
if ((prefixLength % 8) != 0) {
dst[prefixLength/8] = (0xff << (8 - (prefixLength % 8)));
}
}
// Returns a pointer to the first byte in the address data (which is
// stored in network byte order).
uint8_t* sockaddrBytes(int family, sockaddr_storage* ss) {
if (family == AF_INET) {
sockaddr_in* ss4 = reinterpret_cast<sockaddr_in*>(ss);
return reinterpret_cast<uint8_t*>(&ss4->sin_addr);
} else if (family == AF_INET6) {
sockaddr_in6* ss6 = reinterpret_cast<sockaddr_in6*>(ss);
return reinterpret_cast<uint8_t*>(&ss6->sin6_addr);
}
return NULL;
}
private:
// Disallow copy and assignment.
ifaddrs(const ifaddrs&);
void operator=(const ifaddrs&);
};
// FIXME: use iovec instead.
struct addrReq_struct {
nlmsghdr netlinkHeader;
ifaddrmsg msg;
};
inline bool sendNetlinkMessage(int fd, const void* data, size_t byteCount) {
ssize_t sentByteCount = TEMP_FAILURE_RETRY(send(fd, data, byteCount, 0));
return (sentByteCount == static_cast<ssize_t>(byteCount));
}
inline ssize_t recvNetlinkMessage(int fd, char* buf, size_t byteCount) {
return TEMP_FAILURE_RETRY(recv(fd, buf, byteCount, 0));
}
// Source-compatible with the BSD function.
inline int getifaddrs(ifaddrs** result) {
// Simplify cleanup for callers.
*result = NULL;
// Create a netlink socket.
ScopedFd fd(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE));
if (fd.get() < 0) {
return -1;
}
// Ask for the address information.
addrReq_struct addrRequest;
memset(&addrRequest, 0, sizeof(addrRequest));
addrRequest.netlinkHeader.nlmsg_flags = NLM_F_REQUEST | NLM_F_MATCH;
addrRequest.netlinkHeader.nlmsg_type = RTM_GETADDR;
addrRequest.netlinkHeader.nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(sizeof(addrRequest)));
addrRequest.msg.ifa_family = AF_UNSPEC; // All families.
addrRequest.msg.ifa_index = 0; // All interfaces.
if (!sendNetlinkMessage(fd.get(), &addrRequest, addrRequest.netlinkHeader.nlmsg_len)) {
return -1;
}
// Read the responses.
LocalArray<0> buf(65536); // We don't necessarily have std::vector.
ssize_t bytesRead;
while ((bytesRead = recvNetlinkMessage(fd.get(), &buf[0], buf.size())) > 0) {
nlmsghdr* hdr = reinterpret_cast<nlmsghdr*>(&buf[0]);
for (; NLMSG_OK(hdr, (size_t)bytesRead); hdr = NLMSG_NEXT(hdr, bytesRead)) {
switch (hdr->nlmsg_type) {
case NLMSG_DONE:
return 0;
case NLMSG_ERROR:
return -1;
case RTM_NEWADDR:
{
ifaddrmsg* address = reinterpret_cast<ifaddrmsg*>(NLMSG_DATA(hdr));
rtattr* rta = IFA_RTA(address);
size_t ifaPayloadLength = IFA_PAYLOAD(hdr);
while (RTA_OK(rta, ifaPayloadLength)) {
if (rta->rta_type == IFA_LOCAL) {
int family = address->ifa_family;
if (family == AF_INET || family == AF_INET6) {
*result = new ifaddrs(*result);
if (!(*result)->setNameAndFlagsByIndex(address->ifa_index)) {
return -1;
}
(*result)->setAddress(family, RTA_DATA(rta), RTA_PAYLOAD(rta));
(*result)->setNetmask(family, address->ifa_prefixlen);
}
}
rta = RTA_NEXT(rta, ifaPayloadLength);
}
}
break;
}
}
}
// We only get here if recv fails before we see a NLMSG_DONE.
return -1;
}
// Source-compatible with the BSD function.
inline void freeifaddrs(ifaddrs* addresses) {
delete addresses;
}
#endif // IFADDRS_ANDROID_H_included

View file

@ -0,0 +1,98 @@
/*
* RetroShare
* Copyright (C) 2021 Gioacchino Mazzurco <gio@eigenlab.org>
* Copyright (C) 2021 Asociación Civil Altermundi <info@altermundi.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-FileCopyrightText: Retroshare Team <contact@retroshare.cc>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package org.retroshare.service;
import android.util.Log;
import android.content.Context;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
public class AssetHelper
{
public static boolean copyAsset(
Context ctx, String assetPath, String destinationFilePath )
{
Log.d(TAG, "copyAsset " + assetPath + " -> " + destinationFilePath);
InputStream in;
OutputStream out;
try { in = ctx.getAssets().open(assetPath); }
catch(Exception e)
{
Log.e(
TAG,
"Failure opening asset: " + assetPath + " " + e.getMessage() );
return false;
}
try { out = new FileOutputStream(destinationFilePath); }
catch(Exception e)
{
Log.e(
TAG,
"Failure opening destination: " + destinationFilePath + " " +
e.getMessage() );
return false;
}
try
{
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) out.write(buf, 0, len);
}
catch(IOException e)
{
Log.e(
TAG,
"Failure coping: " + assetPath + " -> " + destinationFilePath +
" " + e.getMessage() );
return false;
}
try { in.close(); }
catch(IOException e)
{
Log.e(TAG, "Failure closing: " + assetPath + " " + e.getMessage() );
return false;
}
try { out.close(); }
catch(IOException e)
{
Log.e(
TAG,
"Failure closing: " + destinationFilePath + " " +
e.getMessage() );
return false;
}
return true;
}
private static final String TAG = "RetroShare AssetHelper.java";
}

View file

@ -0,0 +1,48 @@
/*
* RetroShare
* Copyright (C) 2021 Gioacchino Mazzurco <gio@eigenlab.org>
* Copyright (C) 2021 Asociación Civil Altermundi <info@altermundi.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-FileCopyrightText: Retroshare Team <contact@retroshare.cc>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package org.retroshare.service;
public class ErrorConditionWrap
{
public ErrorConditionWrap(
int value, String message, String categoryName )
{
mValue = value;
mMessage = message;
mCategoryName = categoryName;
}
public int value() { return mValue; }
public String message() { return mMessage; }
public String categoryName() { return mCategoryName; }
public boolean toBool() { return mValue != 0; }
@Override
public String toString()
{ return String.format("%d", mValue)+" "+mMessage+" [" + mCategoryName+ "]"; }
private int mValue = 0;
private String mMessage;
private String mCategoryName;
}

View file

@ -0,0 +1,138 @@
/*
* RetroShare
* Copyright (C) 2016-2021 Gioacchino Mazzurco <gio@eigenlab.org>
* Copyright (C) 2021 Asociación Civil Altermundi <info@altermundi.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-FileCopyrightText: Retroshare Team <contact@retroshare.cc>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
package org.retroshare.service;
import android.app.Service;
import android.os.IBinder;
import android.os.Bundle;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.app.ActivityManager;
import java.util.Objects;
public class RetroShareServiceAndroid extends Service
{
public static final int DEFAULT_JSON_API_PORT = 9092;
public static final String DEFAULT_JSON_API_BINDING_ADDRESS = "127.0.0.1";
static { System.loadLibrary("retroshare-service"); }
public static void start(
Context ctx, int jsonApiPort, String jsonApiBindAddress )
{
Log.d(TAG, "start");
Intent intent = new Intent(ctx, RetroShareServiceAndroid.class);
intent.putExtra(JSON_API_PORT_KEY, jsonApiPort);
intent.putExtra(JSON_API_BIND_ADDRESS_KEY, jsonApiBindAddress);
ctx.startService(intent);
}
public static void stop(Context ctx)
{
Log.d(TAG, "stop");
ctx.stopService(new Intent(ctx, RetroShareServiceAndroid.class));
}
public static boolean isRunning(Context ctx)
{
Log.d(TAG, "isRunning");
ActivityManager manager =
(ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
for( ActivityManager.RunningServiceInfo service :
manager.getRunningServices(Integer.MAX_VALUE) )
if( RetroShareServiceAndroid.class.getName()
.equals(service.service.getClassName()) )
return true;
return false;
}
public static Context getServiceContext()
{
if(sServiceContext == null)
Log.e(TAG, "getServiceContext() called before onCreate");
return Objects.requireNonNull(sServiceContext);
}
@Override
public int onStartCommand(
Intent intent, int flags, int startId )
{
if(intent == null)
{
Log.i(TAG, "onStartCommand called without intent");
return Service.START_REDELIVER_INTENT;
}
int jsonApiPort = DEFAULT_JSON_API_PORT;
String jsonApiBindAddress = DEFAULT_JSON_API_BINDING_ADDRESS;
Bundle args = intent.getExtras();
if(args.containsKey(JSON_API_PORT_KEY))
jsonApiPort = args.getInt(JSON_API_PORT_KEY);
if(args.containsKey(JSON_API_BIND_ADDRESS_KEY))
jsonApiBindAddress =
args.getString(JSON_API_BIND_ADDRESS_KEY);
ErrorConditionWrap ec = nativeStart(jsonApiPort, jsonApiBindAddress);
if(ec.toBool()) Log.e(TAG, "onStartCommand(...) " + ec.toString());
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onCreate ()
{
super.onCreate();
sServiceContext = this;
}
@Override
public void onDestroy()
{
ErrorConditionWrap ec = nativeStop();
if(ec.toBool()) Log.e(TAG, "onDestroy() " + ec.toString());
sServiceContext = null;
super.onDestroy();
}
@Override
public IBinder onBind(Intent arg0) { return null; }
private static final String JSON_API_PORT_KEY =
RetroShareServiceAndroid.class.getCanonicalName() +
"/JSON_API_PORT_KEY";
private static final String JSON_API_BIND_ADDRESS_KEY =
RetroShareServiceAndroid.class.getCanonicalName() +
"/JSON_API_BIND_ADDRESS_KEY" ;
private static final String TAG = "RetroShareServiceAndroid.java";
private static Context sServiceContext;
protected static native ErrorConditionWrap nativeStart(
int jsonApiPort, String jsonApiBindAddress );
protected static native ErrorConditionWrap nativeStop();
}

View file

@ -0,0 +1,103 @@
/*
* RetroShare Service Android
* Copyright (C) 2016-2021 Gioacchino Mazzurco <gio@eigenlab.org>
* Copyright (C) 2021 Asociación Civil Altermundi <info@altermundi.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-FileCopyrightText: Retroshare Team <contact@retroshare.cc>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <limits>
#include <cstdint>
#include "util/stacktrace.h"
#include "retroshare/rsinit.h"
#include "retroshare/rsiface.h"
#include "util/rsdebug.h"
#include "rs_android/retroshareserviceandroid.hpp"
#include "rs_android/rsjni.hpp"
/*static*/ std::unique_ptr<AndroidCoutCerrCatcher>
RetroShareServiceAndroid::sAndroidCoutCerrCatcher = nullptr;
using ErrorConditionWrap = RsJni::ErrorConditionWrap;
/*static*/ jni::Local<jni::Object<ErrorConditionWrap>>
RetroShareServiceAndroid::start(
JNIEnv& env, jni::Class<RetroShareServiceAndroid>&,
jni::jint jsonApiPort, const jni::String& jsonApiBindAddress )
{
if(jsonApiPort < 0 || jsonApiPort > std::numeric_limits<uint16_t>::max())
{
RS_ERR("Got invalid JSON API port: ", jsonApiPort);
return jni::Make<ErrorConditionWrap>(env, std::errc::invalid_argument);
}
RsInfo() << "\n" <<
"+================================================================+\n"
"| o---o o |\n"
"| \\ / - Retroshare Service Android - / \\ |\n"
"| o o---o |\n"
"+================================================================+"
<< std::endl << std::endl;
sAndroidCoutCerrCatcher = std::make_unique<AndroidCoutCerrCatcher>();
RsInit::InitRsConfig();
RsControl::earlyInitNotificationSystem();
RsConfigOptions conf;
conf.jsonApiPort = static_cast<uint16_t>(jsonApiPort);
conf.jsonApiBindAddress = jni::Make<std::string>(env, jsonApiBindAddress);
// Dirty workaround plugins not supported on Android ATM
conf.main_executable_path = " ";
int initResult = RsInit::InitRetroShare(conf);
if(initResult != RS_INIT_OK)
{
RS_ERR("Retroshare core initalization failed with: ", initResult);
return jni::Make<ErrorConditionWrap>(env, std::errc::no_child_process);
}
return jni::Make<ErrorConditionWrap>(env, std::error_condition());
}
jni::Local<jni::Object<ErrorConditionWrap>> RetroShareServiceAndroid::stop(
JNIEnv& env, jni::Class<RetroShareServiceAndroid>& )
{
if(RsControl::instance()->isReady())
{
RsControl::instance()->rsGlobalShutDown();
return jni::Make<ErrorConditionWrap>(env, std::error_condition());
}
sAndroidCoutCerrCatcher.reset();
return jni::Make<ErrorConditionWrap>(env, std::errc::no_such_process);
}
jni::Local<jni::Object<RetroShareServiceAndroid::Context> >
RetroShareServiceAndroid::getAndroidContext(JNIEnv& env)
{
auto& clazz = jni::Class<RetroShareServiceAndroid>::Singleton(env);
static auto method =
clazz.GetStaticMethod<jni::Object<RetroShareServiceAndroid::Context>()>(
env, "getServiceContext" );
return clazz.Call(env, method);
}

View file

@ -0,0 +1,84 @@
/*
* RetroShare Service Android
* Copyright (C) 2016-2021 Gioacchino Mazzurco <gio@eigenlab.org>
* Copyright (C) 2021 Asociación Civil Altermundi <info@altermundi.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-FileCopyrightText: Retroshare Team <contact@retroshare.cc>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <system_error>
#include <memory>
#include <jni/jni.hpp>
#include "rs_android/rsjni.hpp"
#include "rs_android/androidcoutcerrcatcher.hpp"
#include "util/stacktrace.h"
/** Provide native methods that are registered into corresponding Java class
* to start/stop RetroShare with reasonable comfort on Android platform */
struct RetroShareServiceAndroid
{
static constexpr auto Name()
{ return "org/retroshare/service/RetroShareServiceAndroid"; }
using ErrorConditionWrap = RsJni::ErrorConditionWrap;
/**
* Called from RetroShareServiceAndroid Java to init libretroshare
* @param[in] env the usual JNI parafernalia
* @param[in] jclass the usual JNI parafernalia
* @param[in] jsonApiPort port on which JSON API server will listen
* @param[in] jsonApiBindAddress binding address of the JSON API server
* @note Yeah you read it well we use a full 32 bit signed integer for JSON
* API port. This is because Java lack even the minimum decency to implement
* unsigned integral types so we need to wrap the port (16 bit unsigned
* integer everywhere reasonable) into a full integer and then check at
* runtime the value.
*/
static jni::Local<jni::Object<ErrorConditionWrap>> start(
JNIEnv& env, jni::Class<RetroShareServiceAndroid>& jclass,
jni::jint jsonApiPort, const jni::String& jsonApiBindAddress );
/**
* Called from RetroShareServiceAndroid Java to shutdown libretroshare
* @param[in] env the usual JNI parafernalia
* @param[in] jclass the usual JNI parafernalia
*/
static jni::Local<jni::Object<ErrorConditionWrap>> stop(
JNIEnv& env, jni::Class<RetroShareServiceAndroid>& );
struct Context
{
/// JNI parafernalia
static constexpr auto Name() { return "android/content/Context"; }
};
/// Return RetroShare Service Android Context
static jni::Local<jni::Object<Context>> getAndroidContext(JNIEnv& env);
private:
/** Doesn't involve complex liftime handling stuff better let the runtime
* handle costruction (ASAP)/destruction for us */
static CrashStackTrace CrashStackTrace;
/** Involve threads, file descriptors etc. better handle lifetime
* explicitely */
static std::unique_ptr<AndroidCoutCerrCatcher> sAndroidCoutCerrCatcher;
};

View file

@ -0,0 +1,70 @@
/*******************************************************************************
* RetroShare JNI utilities *
* *
* libretroshare: retroshare core library *
* *
* Copyright (C) 2021 Gioacchino Mazzurco <gio@eigenlab.org> *
* Copyright (C) 2021 Asociación Civil Altermundi <info@altermundi.net> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#include "rs_android/rsjni.hpp"
#include "rs_android/retroshareserviceandroid.hpp"
rs_view_ptr<JavaVM> RsJni::mJvm = nullptr;
extern "C" JNIEXPORT jint JNICALL JNI_OnLoad_retroshare(JavaVM* vm, void*)
{
RS_DBG(vm);
RsJni::mJvm = vm;
jni::JNIEnv& env { jni::GetEnv(*vm) };
/** Ensure singleton refereces to our own Java classes are inizialized here
* because default Java class loader which is the one accessible by native
* threads which is not main even if attached, is not capable to find them.
* https://stackoverflow.com/questions/20752352/classnotfoundexception-when-finding-a-class-in-jni-background-thread
* https://groups.google.com/g/android-ndk/c/2gkr1mXKn_E */
jni::Class<RsJni::AssetHelper>::Singleton(env);
jni::Class<RsJni::ErrorConditionWrap>::Singleton(env);
jni::RegisterNatives(
env, *jni::Class<RetroShareServiceAndroid>::Singleton(env),
jni::MakeNativeMethod<
decltype(&RetroShareServiceAndroid::start),
&RetroShareServiceAndroid::start >("nativeStart"),
jni::MakeNativeMethod<
decltype(&RetroShareServiceAndroid::stop),
&RetroShareServiceAndroid::stop >("nativeStop")
);
return jni::Unwrap(jni::jni_version_1_2);
}
#ifdef RS_LIBRETROSHARE_EXPORT_JNI_ONLOAD
/** If libretroshare is linked statically to other components which already
* export JNI_OnLoad then a symbol clash may happen
* if RS_LIBRETROSHARE_EXPORT_JNI_ONLOAD is defined.
* @see JNI_OnLoad_retroshare should instead be called from the exported
* JNI_OnLoad */
extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* _reserved)
{
RS_DBG(vm);
return JNI_OnLoad_retroshare(vm, _reserved);
}
#endif // def RS_LIBRETROSHARE_EXPORT_JNI_ONLOAD

View file

@ -0,0 +1,90 @@
/*******************************************************************************
* RetroShare JNI utilities *
* *
* libretroshare: retroshare core library *
* *
* Copyright (C) 2021 Gioacchino Mazzurco <gio@eigenlab.org> *
* Copyright (C) 2021 Asociación Civil Altermundi <info@altermundi.net> *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public License *
* along with this program. If not, see <https://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
#pragma once
#include <system_error>
#include <cstdlib>
#include <jni/jni.hpp>
#include "util/rsmemory.h"
#include "util/cxx23retrocompat.h"
/** Store JVM pointer safely and register native methods */
extern "C" JNIEXPORT jint JNICALL JNI_OnLoad_retroshare(JavaVM* vm, void*);
/** Provide library wide JVM access with some safe measures
* The JVM pointer is set properly by @see JNI_OnLoad_retroshare
*/
class RsJni
{
public:
static inline JavaVM& getVM()
{
if(!mJvm) // [[unlikely]]
{
RS_FATAL( "Attempt to access JVM before JNI_OnLoad_retroshare ",
std::errc::bad_address );
print_stacktrace();
std::exit(std::to_underlying(std::errc::bad_address));
}
return *mJvm;
}
friend jint JNI_OnLoad_retroshare(JavaVM* vm, void*);
/** Provide a comfortable way to access Android package assets like
* bdboot.txt from C++ */
struct AssetHelper
{
static constexpr auto Name()
{ return "org/retroshare/service/AssetHelper"; }
};
/** Provide a comfortable way to propagate C++ error_conditions to Java
* callers */
struct ErrorConditionWrap
{
static constexpr auto Name()
{ return "org/retroshare/service/ErrorConditionWrap"; }
};
private:
static rs_view_ptr<JavaVM> mJvm;
};
namespace jni
{
/** Provides idiomatic way of creating instances via
@code{.cpp}
jni::Make<ErrorConditionWrap>(env, std::error_condition());
@endcode */
jni::Local<jni::Object<RsJni::ErrorConditionWrap>>
MakeAnything(
jni::ThingToMake<RsJni::ErrorConditionWrap>, JNIEnv& env,
const std::error_condition& ec );
}