Browse Source

android ported all + isConnected notif

pull/567/head
hypnosis-i2p 9 years ago
parent
commit
4d3a01a5fe
  1. 11
      android/AndroidManifest.xml
  2. 4
      android/jni/Android.mk
  3. 6
      android/jni/Application.mk
  4. 58
      android/jni/DaemonAndroid.cpp
  5. 21
      android/jni/DaemonAndroid.h
  6. 14
      android/jni/i2pd_android.cpp
  7. 12
      android/jni/org_purplei2p_i2pd_I2PD_JNI.h
  8. 2
      android/project.properties
  9. 16
      android/res/menu/options_main.xml
  10. 5
      android/res/values/strings.xml
  11. 88
      android/src/org/purplei2p/i2pd/ForegroundService.java
  12. 260
      android/src/org/purplei2p/i2pd/I2PD.java
  13. 13
      android/src/org/purplei2p/i2pd/I2PD_JNI.java
  14. 30
      android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java

11
android/AndroidManifest.xml

@ -3,8 +3,16 @@ @@ -3,8 +3,16 @@
package="org.purplei2p.i2pd"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="9" />
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="24"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<application android:label="@string/app_name" android:allowBackup="true" android:icon="@drawable/icon">
<receiver android:name=".NetworkStateChangeReceiver">
<intent-filter>
<action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
</intent-filter>
</receiver>
<activity android:name=".I2PD"
android:label="@string/app_name">
<intent-filter>
@ -12,5 +20,6 @@ @@ -12,5 +20,6 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:enabled="true" android:name=".ForegroundService"/>
</application>
</manifest>

4
android/jni/Android.mk

@ -12,7 +12,6 @@ LOCAL_STATIC_LIBRARIES := \ @@ -12,7 +12,6 @@ LOCAL_STATIC_LIBRARIES := \
miniupnpc
LOCAL_LDLIBS := -lz
#LOCAL_CFLAGS :=
LOCAL_SRC_FILES := DaemonAndroid.cpp i2pd_android.cpp \
$(IFADDRS_PATH)/ifaddrs.c \
../../HTTPServer.cpp ../../I2PControl.cpp ../../Daemon.cpp ../../Config.cpp \
@ -58,9 +57,8 @@ LOCAL_SRC_FILES := DaemonAndroid.cpp i2pd_android.cpp \ @@ -58,9 +57,8 @@ LOCAL_SRC_FILES := DaemonAndroid.cpp i2pd_android.cpp \
../../TunnelEndpoint.cpp \
../../TunnelGateway.cpp \
../../TunnelPool.cpp \
../../UPnP.cpp \
../../util.cpp \
../../i2pd.cpp
../../i2pd.cpp ../../UPnP.cpp
include $(BUILD_SHARED_LIBRARY)

6
android/jni/Application.mk

@ -1,5 +1,7 @@ @@ -1,5 +1,7 @@
#APP_ABI := all
APP_ABI := armeabi-v7a x86
#APP_ABI := armeabi-v7a x86
#APP_ABI := x86
APP_ABI := armeabi-v7a
#can be android-3 but will fail for x86 since arch-x86 is not present at ndkroot/platforms/android-3/ . libz is taken from there.
APP_PLATFORM := android-9
@ -11,7 +13,7 @@ APP_STL := gnustl_shared @@ -11,7 +13,7 @@ APP_STL := gnustl_shared
# Enable c++11 extentions in source code
APP_CPPFLAGS += -std=c++11
APP_CPPFLAGS += -DUSE_UPNP -DANDROID -D__ANDROID__
APP_CPPFLAGS += -DANDROID -D__ANDROID__ -DUSE_UPNP
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
APP_CPPFLAGS += -DANDROID_ARM7A
endif

58
android/jni/DaemonAndroid.cpp

@ -1,5 +1,9 @@ @@ -1,5 +1,9 @@
#include "DaemonAndroid.h"
#include "../../Daemon.h"
#include <iostream>
#include <boost/exception/diagnostic_information.hpp>
#include <boost/exception_ptr.hpp>
#include <exception>
//#include "mainwindow.h"
namespace i2p
@ -58,10 +62,11 @@ namespace android @@ -58,10 +62,11 @@ namespace android
}
}
*/
DaemonAndroidImpl::DaemonAndroidImpl ():
DaemonAndroidImpl::DaemonAndroidImpl ()
//:
/*mutex(nullptr), */
m_IsRunning(false),
m_RunningChangedCallback(nullptr)
//m_IsRunning(false),
//m_RunningChangedCallback(nullptr)
{
}
@ -73,15 +78,15 @@ namespace android @@ -73,15 +78,15 @@ namespace android
bool DaemonAndroidImpl::init(int argc, char* argv[])
{
//mutex=new QMutex(QMutex::Recursive);
setRunningCallback(0);
m_IsRunning=false;
//setRunningCallback(0);
//m_IsRunning=false;
return Daemon.init(argc,argv);
}
void DaemonAndroidImpl::start()
{
//QMutexLocker locker(mutex);
setRunning(true);
//setRunning(true);
Daemon.start();
}
@ -89,7 +94,7 @@ namespace android @@ -89,7 +94,7 @@ namespace android
{
//QMutexLocker locker(mutex);
Daemon.stop();
setRunning(false);
//setRunning(false);
}
void DaemonAndroidImpl::restart()
@ -98,7 +103,7 @@ namespace android @@ -98,7 +103,7 @@ namespace android
stop();
start();
}
/*
void DaemonAndroidImpl::setRunningCallback(runningChangedCallback cb)
{
m_RunningChangedCallback = cb;
@ -119,24 +124,26 @@ namespace android @@ -119,24 +124,26 @@ namespace android
m_RunningChangedCallback();
}
}
*/
static DaemonAndroidImpl daemon;
static char* argv[1]={strdup("tmp")};
/**
* returns 1 if daemon init failed
* returns 0 if daemon initialized and started okay
* returns error details if failed
* returns "ok" if daemon initialized and started okay
*/
int start(/*int argc, char* argv[]*/)
std::string start(/*int argc, char* argv[]*/)
{
try
{
int result;
//int result;
{
//Log.d(TAG"Initialising the daemon...");
bool daemonInitSuccess = daemon.init(0,0/*argc, argv*/);
bool daemonInitSuccess = daemon.init(1,argv);
if(!daemonInitSuccess)
{
//QMessageBox::critical(0, "Error", "Daemon init failed");
return 1;
return "Daemon init failed";
}
//Log.d(TAG"Initialised, creating the main window...");
//MainWindow w;
@ -152,7 +159,6 @@ namespace android @@ -152,7 +159,6 @@ namespace android
//result = app.exec();
//daemon.stop ();
daemon.start();
return 0;
}
}
@ -160,6 +166,24 @@ namespace android @@ -160,6 +166,24 @@ namespace android
//Log.d(TAG"Exiting the application");
//return result;
}
catch (boost::exception& ex)
{
std::stringstream ss;
ss << boost::diagnostic_information(ex);
return ss.str();
}
catch (std::exception& ex)
{
std::stringstream ss;
ss << ex.what();
return ss.str();
}
catch(...)
{
return "unknown exception";
}
return "ok";
}
void stop()
{

21
android/jni/DaemonAndroid.h

@ -1,11 +1,12 @@ @@ -1,11 +1,12 @@
#ifndef DAEMON_ANDROID_H
#define DAEMON_ANDROID_H
#include <string>
namespace i2p
{
namespace android
{
//FIXME currently NOT threadsafe
class DaemonAndroidImpl
{
public:
@ -13,7 +14,7 @@ namespace android @@ -13,7 +14,7 @@ namespace android
DaemonAndroidImpl ();
~DaemonAndroidImpl ();
typedef void (*runningChangedCallback)();
//typedef void (*runningChangedCallback)();
/**
* @return success
@ -22,21 +23,21 @@ namespace android @@ -22,21 +23,21 @@ namespace android
void start();
void stop();
void restart();
void setRunningCallback(runningChangedCallback cb);
bool isRunning();
//void setRunningCallback(runningChangedCallback cb);
//bool isRunning();
private:
void setRunning(bool running);
//void setRunning(bool running);
private:
//QMutex* mutex;
bool m_IsRunning;
runningChangedCallback m_RunningChangedCallback;
//bool m_IsRunning;
//runningChangedCallback m_RunningChangedCallback;
};
/**
* returns 1 if daemon init failed
* returns 0 if daemon initialized and started okay
* returns "ok" if daemon init failed
* returns errinfo if daemon initialized and started okay
*/
int start();
std::string start();
// stops the daemon
void stop();

14
android/jni/i2pd_android.cpp

@ -3,6 +3,7 @@ @@ -3,6 +3,7 @@
#include <jni.h>
#include "org_purplei2p_i2pd_I2PD_JNI.h"
#include "DaemonAndroid.h"
#include "../../RouterContext.h"
JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith
(JNIEnv * env, jclass clazz) {
@ -41,9 +42,9 @@ JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith @@ -41,9 +42,9 @@ JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith
return env->NewStringUTF(ABI);
}
JNIEXPORT jint JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon
JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon
(JNIEnv * env, jclass clazz) {
return (jint)i2p::android::start();
return env->NewStringUTF(i2p::android::start().c_str());
}
JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon
@ -51,3 +52,12 @@ JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon @@ -51,3 +52,12 @@ JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon
i2p::android::stop();
}
JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopAcceptingTunnels
(JNIEnv * env, jclass clazz) {
i2p::context.SetAcceptsTunnels (false);
}
JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged
(JNIEnv * env, jclass clazz, jboolean isConnected) {
bool isConnectedBool = (bool) isConnected;
}

12
android/jni/org_purplei2p_i2pd_I2PD_JNI.h

@ -15,6 +15,18 @@ extern "C" { @@ -15,6 +15,18 @@ extern "C" {
JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_getABICompiledWith
(JNIEnv *, jclass);
JNIEXPORT jstring JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_startDaemon
(JNIEnv *, jclass);
JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopDaemon
(JNIEnv *, jclass);
JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_stopAcceptingTunnels
(JNIEnv *, jclass);
JNIEXPORT void JNICALL Java_org_purplei2p_i2pd_I2PD_1JNI_onNetworkStateChanged
(JNIEnv * env, jclass clazz, jboolean isConnected);
#ifdef __cplusplus
}
#endif

2
android/project.properties

@ -11,4 +11,4 @@ @@ -11,4 +11,4 @@
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
target=android-9
target=android-24

16
android/res/menu/options_main.xml

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".I2PD">
<item
android:id="@+id/action_graceful_quit"
android:title="@string/action_graceful_quit"
android:orderInCategory="98"
/>
<item
android:id="@+id/action_quit"
android:title="@string/action_quit"
android:orderInCategory="99"
/>
</menu>

5
android/res/values/strings.xml

@ -1,4 +1,9 @@ @@ -1,4 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">i2pd</string>
<string name="i2pd_started">i2pd started</string>
<string name="action_quit">Quit</string>
<string name="action_graceful_quit">Graceful Quit</string>
<string name="graceful_quit_is_already_in_progress">Graceful quit is already in progress</string>
<string name="graceful_quit_is_in_progress">Graceful quit is in progress</string>
</resources>

88
android/src/org/purplei2p/i2pd/ForegroundService.java

@ -0,0 +1,88 @@ @@ -0,0 +1,88 @@
package org.purplei2p.i2pd;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
public class ForegroundService extends Service {
// private NotificationManager mNM;
// Unique Identification Number for the Notification.
// We use it on Notification start, and to cancel it.
private int NOTIFICATION = R.string.i2pd_started;
/**
* Class for clients to access. Because we know this service always
* runs in the same process as its clients, we don't need to deal with
* IPC.
*/
public class LocalBinder extends Binder {
ForegroundService getService() {
return ForegroundService.this;
}
}
@Override
public void onCreate() {
// mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
// Display a notification about us starting. We put an icon in the status bar.
showNotification();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i("ForegroundService", "Received start id " + startId + ": " + intent);
return START_NOT_STICKY;
}
@Override
public void onDestroy() {
// Cancel the persistent notification.
//mNM.cancel(NOTIFICATION);
stopForeground(true);
// Tell the user we stopped.
//Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show();
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
// This is the object that receives interactions from clients. See
// RemoteService for a more complete example.
private final IBinder mBinder = new LocalBinder();
/**
* Show a notification while this service is running.
*/
private void showNotification() {
// In this sample, we'll use the same text for the ticker and the expanded notification
CharSequence text = getText(R.string.i2pd_started);
// The PendingIntent to launch our activity if the user selects this notification
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, I2PD.class), 0);
// Set the info for the views that show in the notification panel.
Notification notification = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.itoopie_notification_icon) // the status icon
.setTicker(text) // the status text
.setWhen(System.currentTimeMillis()) // the time stamp
.setContentTitle(getText(R.string.app_name)) // the label of the entry
.setContentText(text) // the contents of the entry
.setContentIntent(contentIntent) // The intent to send when the entry is clicked
.build();
// Send the notification.
//mNM.notify(NOTIFICATION, notification);
startForeground(NOTIFICATION, notification);
}
}

260
android/src/org/purplei2p/i2pd/I2PD.java

@ -1,20 +1,272 @@ @@ -1,20 +1,272 @@
package org.purplei2p.i2pd;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Timer;
import java.util.TimerTask;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.widget.TextView;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;
public class I2PD extends Activity {
private static final String TAG = "i2pd";
private static Throwable loadLibsThrowable;
static {
try {
I2PD_JNI.loadLibraries();
} catch (Throwable tr) {
loadLibsThrowable = tr;
}
}
private String daemonStartResult="N/A";
private boolean destroyed=false;
private TextView textView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
destroyed=false;
//set the app be foreground (do not unload when RAM needed)
doBindService();
textView = new TextView(this);
setContentView(textView);
if (loadLibsThrowable != null) {
textView.setText(throwableToString(loadLibsThrowable)+"\r\n");
return;
}
try {
textView.setText(
"libi2pd.so was compiled with ABI " + getABICompiledWith() + "\r\n"+
"Starting daemon... ");
new Thread(new Runnable(){
@Override
public void run() {
try {
doStartDaemon();
} catch (final Throwable tr) {
appendThrowable(tr);
}
}
},"i2pdDaemonStarting").start();
} catch (Throwable tr) {
textView.setText(textView.getText().toString()+throwableToString(tr));
}
}
@Override
protected void onDestroy() {
super.onDestroy();
localDestroy();
}
TextView tv = new TextView(this);
tv.setText( "libi2pd.so was compiled with ABI " + getABICompiledWith());
setContentView(tv);
private synchronized void localDestroy() {
if(destroyed)return;
destroyed=true;
if(gracefulQuitTimer!=null) {
gracefulQuitTimer.cancel();
gracefulQuitTimer = null;
}
try{
doUnbindService();
}catch(Throwable tr){
Log.e(TAG, "", tr);
}
if("ok".equals(daemonStartResult)) {
try {
I2PD_JNI.stopDaemon();
} catch (Throwable tr) {
Log.e(TAG, "error", tr);
}
}
}
private CharSequence throwableToString(Throwable tr) {
StringWriter sw = new StringWriter(8192);
PrintWriter pw = new PrintWriter(sw);
tr.printStackTrace(pw);
pw.close();
return sw.toString();
}
public String getABICompiledWith() {
return I2PD_JNI.getABICompiledWith();
}
private synchronized void doStartDaemon() {
if(destroyed)return;
daemonStartResult = I2PD_JNI.startDaemon();
runOnUiThread(new Runnable(){
@Override
public void run() {
synchronized (I2PD.this) {
if(destroyed)return;
textView.setText(
textView.getText().toString()+
"start result: "+daemonStartResult+"\r\n"
);
}
}
});
}
private void appendThrowable(final Throwable tr) {
runOnUiThread(new Runnable(){
@Override
public void run() {
synchronized (I2PD.this) {
if(destroyed)return;
textView.setText(textView.getText().toString()+throwableToString(tr)+"\r\n");
}
}
});
}
// private LocalService mBoundService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. Because we have bound to a explicit
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
// mBoundService = ((LocalService.LocalBinder)service).getService();
// Tell the user about this for our demo.
// Toast.makeText(Binding.this, R.string.local_service_connected,
// Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
// Because it is running in our same process, we should never
// see this happen.
// mBoundService = null;
// Toast.makeText(Binding.this, R.string.local_service_disconnected,
// Toast.LENGTH_SHORT).show();
}
};
private boolean mIsBound;
private void doBindService() {
// Establish a connection with the service. We use an explicit
// class name because we want a specific service implementation that
// we know will be running in our own process (and thus won't be
// supporting component replacement by other applications).
bindService(new Intent(this,
ForegroundService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
private void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.options_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
switch(id){
case R.id.action_quit:
quit();
return true;
case R.id.action_graceful_quit:
gracefulQuit();
return true;
}
return super.onOptionsItemSelected(item);
}
@SuppressLint("NewApi")
private void quit() {
try {
localDestroy();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
finishAndRemoveTask();
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
finishAffinity();
} else {
//moveTaskToBack(true);
finish();
}
}catch (Throwable tr) {
Log.e(TAG, "", tr);
}
}
private Timer gracefulQuitTimer;
private synchronized void gracefulQuit() {
if(gracefulQuitTimer!=null){
Toast.makeText(this, R.string.graceful_quit_is_already_in_progress,
Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(this, R.string.graceful_quit_is_in_progress,
Toast.LENGTH_SHORT).show();
new Thread(new Runnable(){
@Override
public void run() {
try{
synchronized (I2PD.this) {
if("ok".equals(daemonStartResult)) {
I2PD_JNI.stopAcceptingTunnels();
gracefulQuitTimer = new Timer(true);
gracefulQuitTimer.schedule(new TimerTask(){
@Override
public void run() {
quit();
}
}, 10*60*1000/*millis*/);
}else{
quit();
}
}
} catch(Throwable tr) {
Log.e(TAG,"",tr);
}
}
},"gracQuitInit").start();
}
}

13
android/src/org/purplei2p/i2pd/I2PD_JNI.java

@ -3,14 +3,19 @@ package org.purplei2p.i2pd; @@ -3,14 +3,19 @@ package org.purplei2p.i2pd;
public class I2PD_JNI {
public static native String getABICompiledWith();
/**
* returns 1 if daemon init failed
* returns 0 if daemon initialized and started okay
* returns error info if failed
* returns "ok" if daemon initialized and started okay
*/
public static native int startDaemon();
public static native String startDaemon();
//should only be called after startDaemon() success
public static native void stopDaemon();
static {
public static native void stopAcceptingTunnels();
public static native void onNetworkStateChanged(boolean isConnected);
public static void loadLibraries() {
//System.loadLibrary("gnustl_shared");
System.loadLibrary("i2pd");
}
}

30
android/src/org/purplei2p/i2pd/NetworkStateChangeReceiver.java

@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
package org.purplei2p.i2pd;
import android.util.Log;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
public class NetworkStateChangeReceiver extends BroadcastReceiver {
private static final String TAG = "i2pd";
//api level 1
@Override
public void onReceive(final Context context, final Intent intent) {
Log.d(TAG,"Network state change");
try {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = cm.getActiveNetworkInfo();
boolean isConnected = activeNetworkInfo!=null && activeNetworkInfo.isConnected();
// https://developer.android.com/training/monitoring-device-state/connectivity-monitoring.html?hl=ru
// boolean isWiFi = activeNetworkInfo!=null && (activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI);
I2PD_JNI.onNetworkStateChanged(isConnected);
} catch (Throwable tr) {
Log.d(TAG,"",tr);
}
}
}
Loading…
Cancel
Save