From d4110e64d95044f952e046dc8c724791a7272d98 Mon Sep 17 00:00:00 2001 From: R4SAS Date: Thu, 15 Oct 2020 04:31:35 +0300 Subject: [PATCH] i2pd 2.33.0-63, tabulation, gradle configuration Signed-off-by: R4SAS --- app/build.gradle | 21 +- app/jni/i2pd | 2 +- .../org/purplei2p/i2pd/DaemonSingleton.java | 343 ++--- .../org/purplei2p/i2pd/ForegroundService.java | 260 ++-- .../java/org/purplei2p/i2pd/I2PDActivity.java | 1187 ++++++++--------- .../i2pd/I2PDPermsAskerActivity.java | 305 ++--- .../i2pd/I2PDPermsExplanationActivity.java | 48 +- .../java/org/purplei2p/i2pd/I2PD_JNI.java | 36 +- .../i2pd/NetworkStateChangeReceiver.java | 33 +- app/src/main/res/values/strings.xml | 2 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 12 +- settings.gradle | 1 + 13 files changed, 1128 insertions(+), 1124 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 02118ae..5346400 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,7 +3,7 @@ plugins { } dependencies { - implementation 'androidx.core:core:1.0.2' + implementation 'androidx.core:core:1.3.0' } android { @@ -14,8 +14,8 @@ android { applicationId "org.purplei2p.i2pd" targetSdkVersion 29 minSdkVersion 14 - versionCode 23210 - versionName "2.32.1" + versionCode 23300 + versionName "2.33.0-63-g2648f1ba" setProperty("archivesBaseName", archivesBaseName + "-" + versionName) ndk { @@ -39,14 +39,13 @@ android { enable true reset() include "armeabi-v7a", "arm64-v8a", "x86", "x86_64" - //include "armeabi-v7a", "x86" universalApk true } } signingConfigs { - orignal { - storeFile file("i2pdapk.jks") + release { + storeFile file('i2pdapk.jks') storePassword "android" keyAlias "i2pdapk" keyPassword "android" @@ -55,10 +54,12 @@ android { buildTypes { release { - minifyEnabled false - signingConfig signingConfigs.orignal + signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' } + debug { + jniDebuggable = true + } } externalNativeBuild { @@ -68,8 +69,8 @@ android { } compileOptions { - sourceCompatibility = '1.8' - targetCompatibility = '1.8' + sourceCompatibility = "1.8" + targetCompatibility = "1.8" } } diff --git a/app/jni/i2pd b/app/jni/i2pd index 6735b26..2648f1b 160000 --- a/app/jni/i2pd +++ b/app/jni/i2pd @@ -1 +1 @@ -Subproject commit 6735b2686b6c13a36546dd794ee49b4d583565e0 +Subproject commit 2648f1ba89d5032262a72ca8b2d2d8a70e441b9a diff --git a/app/src/main/java/org/purplei2p/i2pd/DaemonSingleton.java b/app/src/main/java/org/purplei2p/i2pd/DaemonSingleton.java index e9e4fc0..d2e829b 100644 --- a/app/src/main/java/org/purplei2p/i2pd/DaemonSingleton.java +++ b/app/src/main/java/org/purplei2p/i2pd/DaemonSingleton.java @@ -2,180 +2,183 @@ package org.purplei2p.i2pd; import java.util.HashSet; import java.util.Set; + import android.os.Environment; import android.util.Log; import org.purplei2p.i2pd.R; public class DaemonSingleton { - private static final String TAG = "i2pd"; - private static final DaemonSingleton instance = new DaemonSingleton(); - - public interface StateUpdateListener { - void daemonStateUpdate(); - } - - private final Set stateUpdateListeners = new HashSet<>(); - - public static DaemonSingleton getInstance() { - return instance; - } - - public synchronized void addStateChangeListener(StateUpdateListener listener) { - stateUpdateListeners.add(listener); - } - - public synchronized void removeStateChangeListener(StateUpdateListener listener) { - stateUpdateListeners.remove(listener); - } - - private synchronized void setState(State newState) { - if (newState == null) - throw new NullPointerException(); - - State oldState = state; - - if (oldState == null) - throw new NullPointerException(); - - if (oldState.equals(newState)) - return; - - state = newState; - fireStateUpdate1(); - } - - public synchronized void stopAcceptingTunnels() { - if (isStartedOkay()) { - setState(State.gracefulShutdownInProgress); - I2PD_JNI.stopAcceptingTunnels(); - } - } - - public synchronized void startAcceptingTunnels() { - if (isStartedOkay()) { - setState(State.startedOkay); - I2PD_JNI.startAcceptingTunnels(); - } - } - - public synchronized void reloadTunnelsConfigs() { - if (isStartedOkay()) { - I2PD_JNI.reloadTunnelsConfigs(); - } - } - - public synchronized int GetTransitTunnelsCount() { - return I2PD_JNI.GetTransitTunnelsCount(); - } - - private volatile boolean startedOkay; - - public enum State { - uninitialized(R.string.uninitialized), - starting(R.string.starting), - jniLibraryLoaded(R.string.jniLibraryLoaded), - startedOkay(R.string.startedOkay), - startFailed(R.string.startFailed), - gracefulShutdownInProgress(R.string.gracefulShutdownInProgress), - stopped(R.string.stopped); - - State(int statusStringResourceId) { - this.statusStringResourceId = statusStringResourceId; - } - - private final int statusStringResourceId; - - public int getStatusStringResourceId() { - return statusStringResourceId; - } - }; - - private volatile State state = State.uninitialized; - - public State getState() { - return state; - } - - { - setState(State.starting); - new Thread(new Runnable() { - - @Override - public void run() { - try { - I2PD_JNI.loadLibraries(); - setState(State.jniLibraryLoaded); - } catch (Throwable tr) { - lastThrowable = tr; - setState(State.startFailed); - return; - } - try { - synchronized (DaemonSingleton.this) { - I2PD_JNI.setDataDir(Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd"); - daemonStartResult = I2PD_JNI.startDaemon(); - if ("ok".equals(daemonStartResult)) { - setState(State.startedOkay); - setStartedOkay(true); - } else - setState(State.startFailed); - } - } catch (Throwable tr) { - lastThrowable = tr; - setState(State.startFailed); - } - } - - }, "i2pdDaemonStart").start(); - } - - private Throwable lastThrowable; - private String daemonStartResult = "N/A"; - - private void fireStateUpdate1() { - Log.i(TAG, "daemon state change: " + state); - for (StateUpdateListener listener : stateUpdateListeners) { - try { - listener.daemonStateUpdate(); - } catch (Throwable tr) { - Log.e(TAG, "exception in listener ignored", tr); - } - } - } - - public Throwable getLastThrowable() { - return lastThrowable; - } - - public String getDaemonStartResult() { - return daemonStartResult; - } - - private final Object startedOkayLock = new Object(); - - public boolean isStartedOkay() { - synchronized (startedOkayLock) { - return startedOkay; - } - } - - private void setStartedOkay(boolean startedOkay) { - synchronized (startedOkayLock) { - this.startedOkay = startedOkay; - } - } - - public synchronized void stopDaemon() { - if (isStartedOkay()) { - try { - I2PD_JNI.stopDaemon(); - } catch(Throwable tr) { - Log.e(TAG, "", tr); - } - - setStartedOkay(false); - setState(State.stopped); - } - } + private static final String TAG = "i2pd"; + private static final DaemonSingleton instance = new DaemonSingleton(); + + public interface StateUpdateListener { + void daemonStateUpdate(); + } + + private final Set stateUpdateListeners = new HashSet<>(); + + public static DaemonSingleton getInstance() { + return instance; + } + + public synchronized void addStateChangeListener(StateUpdateListener listener) { + stateUpdateListeners.add(listener); + } + + public synchronized void removeStateChangeListener(StateUpdateListener listener) { + stateUpdateListeners.remove(listener); + } + + private synchronized void setState(State newState) { + if (newState == null) + throw new NullPointerException(); + + State oldState = state; + + if (oldState == null) + throw new NullPointerException(); + + if (oldState.equals(newState)) + return; + + state = newState; + fireStateUpdate1(); + } + + public synchronized void stopAcceptingTunnels() { + if (isStartedOkay()) { + setState(State.gracefulShutdownInProgress); + I2PD_JNI.stopAcceptingTunnels(); + } + } + + public synchronized void startAcceptingTunnels() { + if (isStartedOkay()) { + setState(State.startedOkay); + I2PD_JNI.startAcceptingTunnels(); + } + } + + public synchronized void reloadTunnelsConfigs() { + if (isStartedOkay()) { + I2PD_JNI.reloadTunnelsConfigs(); + } + } + + public synchronized int GetTransitTunnelsCount() { + return I2PD_JNI.GetTransitTunnelsCount(); + } + + private volatile boolean startedOkay; + + public enum State { + uninitialized(R.string.uninitialized), + starting(R.string.starting), + jniLibraryLoaded(R.string.jniLibraryLoaded), + startedOkay(R.string.startedOkay), + startFailed(R.string.startFailed), + gracefulShutdownInProgress(R.string.gracefulShutdownInProgress), + stopped(R.string.stopped); + + State(int statusStringResourceId) { + this.statusStringResourceId = statusStringResourceId; + } + + private final int statusStringResourceId; + + public int getStatusStringResourceId() { + return statusStringResourceId; + } + } + + ; + + private volatile State state = State.uninitialized; + + public State getState() { + return state; + } + + { + setState(State.starting); + new Thread(new Runnable() { + + @Override + public void run() { + try { + I2PD_JNI.loadLibraries(); + setState(State.jniLibraryLoaded); + } catch (Throwable tr) { + lastThrowable = tr; + setState(State.startFailed); + return; + } + try { + synchronized (DaemonSingleton.this) { + I2PD_JNI.setDataDir(Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd"); + daemonStartResult = I2PD_JNI.startDaemon(); + if ("ok".equals(daemonStartResult)) { + setState(State.startedOkay); + setStartedOkay(true); + } else + setState(State.startFailed); + } + } catch (Throwable tr) { + lastThrowable = tr; + setState(State.startFailed); + } + } + + }, "i2pdDaemonStart").start(); + } + + private Throwable lastThrowable; + private String daemonStartResult = "N/A"; + + private void fireStateUpdate1() { + Log.i(TAG, "daemon state change: " + state); + for (StateUpdateListener listener : stateUpdateListeners) { + try { + listener.daemonStateUpdate(); + } catch (Throwable tr) { + Log.e(TAG, "exception in listener ignored", tr); + } + } + } + + public Throwable getLastThrowable() { + return lastThrowable; + } + + public String getDaemonStartResult() { + return daemonStartResult; + } + + private final Object startedOkayLock = new Object(); + + public boolean isStartedOkay() { + synchronized (startedOkayLock) { + return startedOkay; + } + } + + private void setStartedOkay(boolean startedOkay) { + synchronized (startedOkayLock) { + this.startedOkay = startedOkay; + } + } + + public synchronized void stopDaemon() { + if (isStartedOkay()) { + try { + I2PD_JNI.stopDaemon(); + } catch (Throwable tr) { + Log.e(TAG, "", tr); + } + + setStartedOkay(false); + setState(State.stopped); + } + } } diff --git a/app/src/main/java/org/purplei2p/i2pd/ForegroundService.java b/app/src/main/java/org/purplei2p/i2pd/ForegroundService.java index c1b1cc2..a048711 100644 --- a/app/src/main/java/org/purplei2p/i2pd/ForegroundService.java +++ b/app/src/main/java/org/purplei2p/i2pd/ForegroundService.java @@ -10,140 +10,144 @@ import android.content.Intent; import android.os.Binder; import android.os.Build; import android.os.IBinder; + import androidx.annotation.RequiresApi; import androidx.core.app.NotificationCompat; + import android.util.Log; public class ForegroundService extends Service { - private static final String TAG="FgService"; - - private volatile boolean shown; - - private final DaemonSingleton.StateUpdateListener daemonStateUpdatedListener = - new DaemonSingleton.StateUpdateListener() { - - @Override - public void daemonStateUpdate() { - try { - synchronized (ForegroundService.this) { - if (shown) cancelNotification(); - showNotification(); - } - } catch (Throwable tr) { - Log.e(TAG,"error ignored",tr); - } - } - }; - - - private NotificationManager notificationManager; - - // Unique Identification Number for the Notification. - // We use it on Notification start, and to cancel it. - private int NOTIFICATION = 1; - - /** - * 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() { - notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); - - synchronized (this) { - DaemonSingleton.getInstance().addStateChangeListener(daemonStateUpdatedListener); - if (!shown) daemonStateUpdatedListener.daemonStateUpdate(); - } - // Tell the user we started. + private static final String TAG = "FgService"; + + private volatile boolean shown; + + private final DaemonSingleton.StateUpdateListener daemonStateUpdatedListener = + new DaemonSingleton.StateUpdateListener() { + + @Override + public void daemonStateUpdate() { + try { + synchronized (ForegroundService.this) { + if (shown) cancelNotification(); + showNotification(); + } + } catch (Throwable tr) { + Log.e(TAG, "error ignored", tr); + } + } + }; + + + private NotificationManager notificationManager; + + // Unique Identification Number for the Notification. + // We use it on Notification start, and to cancel it. + private int NOTIFICATION = 1; + + /** + * 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() { + notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + + synchronized (this) { + DaemonSingleton.getInstance().addStateChangeListener(daemonStateUpdatedListener); + if (!shown) daemonStateUpdatedListener.daemonStateUpdate(); + } + // Tell the user we started. // Toast.makeText(this, R.string.i2pd_service_started, Toast.LENGTH_SHORT).show(); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - Log.i("ForegroundService", "Received start id " + startId + ": " + intent); - return START_STICKY; - } - - @Override - public void onDestroy() { - DaemonSingleton.getInstance().removeStateChangeListener(daemonStateUpdatedListener); - cancelNotification(); - } - - private synchronized void cancelNotification() { - // Cancel the persistent notification. - notificationManager.cancel(NOTIFICATION); - - stopForeground(true); - - // Tell the user we stopped. - //Toast.makeText(this, R.string.i2pd_service_stopped, Toast.LENGTH_SHORT).show(); - shown=false; - } - - @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 synchronized void showNotification() { - // In this sample, we'll use the same text for the ticker and the expanded notification - CharSequence text = getText(DaemonSingleton.getInstance().getState().getStatusStringResourceId()); - - // The PendingIntent to launch our activity if the user selects this notification - PendingIntent contentIntent = PendingIntent.getActivity(this, 0, - new Intent(this, I2PDActivity.class), 0); - - // If earlier version channel ID is not used - // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) - String channelId = Build.VERSION.SDK_INT >= 26 ? createNotificationChannel() : ""; - - // Set the info for the views that show in the notification panel. - NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId) - .setOngoing(true) - .setSmallIcon(R.drawable.itoopie_notification_icon); // the status icon - if(Build.VERSION.SDK_INT >= 16) builder = builder.setPriority(Notification.PRIORITY_DEFAULT); - if(Build.VERSION.SDK_INT >= 21) builder = builder.setCategory(Notification.CATEGORY_SERVICE); - Notification notification = builder - .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); - shown = true; - } - - @RequiresApi(Build.VERSION_CODES.O) - private synchronized String createNotificationChannel() { - String channelId = getString(R.string.app_name); - CharSequence channelName = "I2Pd service"; - NotificationChannel chan = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW); - //chan.setLightColor(Color.PURPLE); - chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); - NotificationManager service = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); - if(service!=null)service.createNotificationChannel(chan); - else Log.e(TAG, "error: NOTIFICATION_SERVICE is null"); - return channelId; - } + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.i("ForegroundService", "Received start id " + startId + ": " + intent); + return START_STICKY; + } + + @Override + public void onDestroy() { + DaemonSingleton.getInstance().removeStateChangeListener(daemonStateUpdatedListener); + cancelNotification(); + } + + private synchronized void cancelNotification() { + // Cancel the persistent notification. + notificationManager.cancel(NOTIFICATION); + + stopForeground(true); + + // Tell the user we stopped. + //Toast.makeText(this, R.string.i2pd_service_stopped, Toast.LENGTH_SHORT).show(); + shown = false; + } + + @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 synchronized void showNotification() { + // In this sample, we'll use the same text for the ticker and the expanded notification + CharSequence text = getText(DaemonSingleton.getInstance().getState().getStatusStringResourceId()); + + // The PendingIntent to launch our activity if the user selects this notification + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, + new Intent(this, I2PDActivity.class), 0); + + // If earlier version channel ID is not used + // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) + String channelId = Build.VERSION.SDK_INT >= 26 ? createNotificationChannel() : ""; + + // Set the info for the views that show in the notification panel. + NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId) + .setOngoing(true) + .setSmallIcon(R.drawable.itoopie_notification_icon); // the status icon + if (Build.VERSION.SDK_INT >= 16) + builder = builder.setPriority(Notification.PRIORITY_DEFAULT); + if (Build.VERSION.SDK_INT >= 21) + builder = builder.setCategory(Notification.CATEGORY_SERVICE); + Notification notification = builder + .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); + shown = true; + } + + @RequiresApi(Build.VERSION_CODES.O) + private synchronized String createNotificationChannel() { + String channelId = getString(R.string.app_name); + CharSequence channelName = "I2Pd service"; + NotificationChannel chan = new NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_LOW); + //chan.setLightColor(Color.PURPLE); + chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); + NotificationManager service = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + if (service != null) service.createNotificationChannel(chan); + else Log.e(TAG, "error: NOTIFICATION_SERVICE is null"); + return channelId; + } private static final DaemonSingleton daemon = DaemonSingleton.getInstance(); } diff --git a/app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java b/app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java index 8295e9f..262c488 100644 --- a/app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java +++ b/app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java @@ -54,601 +54,594 @@ import android.webkit.WebViewClient; import static android.provider.Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS; public class I2PDActivity extends Activity { - private WebView webView; - - private static final String TAG = "i2pdActvt"; - private static final int MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 1; - public static final int GRACEFUL_DELAY_MILLIS = 10 * 60 * 1000; - public static final String PACKAGE_URI_SCHEME = "package:"; - - private TextView textView; - private boolean assetsCopied; - private String i2pdpath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd/"; - //private ConfigParser parser = new ConfigParser(i2pdpath); // TODO: - - private static final DaemonSingleton daemon = DaemonSingleton.getInstance(); - - private final DaemonSingleton.StateUpdateListener daemonStateUpdatedListener = new DaemonSingleton.StateUpdateListener() { - @Override - public void daemonStateUpdate() { - processAssets(); - runOnUiThread(() -> { - try { - if (textView == null) - return; - Throwable tr = daemon.getLastThrowable(); - if (tr!=null) { - textView.setText(throwableToString(tr)); - return; - } - DaemonSingleton.State state = daemon.getState(); - String startResultStr = DaemonSingleton.State.startFailed.equals(state) ? String.format(": %s", daemon.getDaemonStartResult()) : ""; - String graceStr = DaemonSingleton.State.gracefulShutdownInProgress.equals(state) ? String.format(": %s %s", formatGraceTimeRemaining(), getText(R.string.remaining)) : ""; - textView.setText(String.format("%s%s%s", getText(state.getStatusStringResourceId()), startResultStr, graceStr)); - } catch (Throwable tr) { - Log.e(TAG,"error ignored",tr); - } - }); - } - }; - private static volatile long graceStartedMillis; - private static final Object graceStartedMillis_LOCK = new Object(); - private Menu optionsMenu; - - private static String formatGraceTimeRemaining() { - long remainingSeconds; - synchronized (graceStartedMillis_LOCK) { - remainingSeconds = Math.round(Math.max(0, graceStartedMillis + GRACEFUL_DELAY_MILLIS - System.currentTimeMillis()) / 1000.0D); - } - long remainingMinutes = (long)Math.floor(remainingSeconds / 60.0D); - long remSec = remainingSeconds - remainingMinutes * 60; - return remainingMinutes + ":" + (remSec / 10) + remSec % 10; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - Log.i(TAG, "onCreate"); - super.onCreate(savedInstanceState); - - textView = new TextView(this); - setContentView(textView); - daemon.addStateChangeListener(daemonStateUpdatedListener); - daemonStateUpdatedListener.daemonStateUpdate(); - - // request permissions - if (Build.VERSION.SDK_INT >= 23) { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - ActivityCompat.requestPermissions(this, - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE); - } - } - - // set the app be foreground - doBindService(); - - final Timer gracefulQuitTimer = getGracefulQuitTimer(); - if (gracefulQuitTimer != null) { - long gracefulStopAtMillis; - synchronized (graceStartedMillis_LOCK) { - gracefulStopAtMillis = graceStartedMillis + GRACEFUL_DELAY_MILLIS; - } - rescheduleGraceStop(gracefulQuitTimer, gracefulStopAtMillis); - } - - openBatteryOptimizationDialogIfNeeded(); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - textView = null; - daemon.removeStateChangeListener(daemonStateUpdatedListener); - //cancelGracefulStop0(); - try { - doUnbindService(); - } catch(Throwable tr) { - Log.e(TAG, "", tr); - } - } - - @Override - public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) - { - if (requestCode == MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE) { - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) - Log.e(TAG, "WR_EXT_STORAGE perm granted"); - else { - Log.e(TAG, "WR_EXT_STORAGE perm declined, stopping i2pd"); - i2pdStop(); - //TODO must work w/o this perm, ask orignal - } - } - } - - private void cancelGracefulStop0() { - Timer gracefulQuitTimer = getGracefulQuitTimer(); - if (gracefulQuitTimer != null) { - gracefulQuitTimer.cancel(); - setGracefulQuitTimer(null); - } - } - - private CharSequence throwableToString(Throwable tr) { - StringWriter sw = new StringWriter(8192); - PrintWriter pw = new PrintWriter(sw); - tr.printStackTrace(pw); - pw.close(); - return sw.toString(); - } - - // 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 static volatile boolean mIsBound; - - private void doBindService() { - synchronized (I2PDActivity.class) { - if (mIsBound) - return; - // 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() { - synchronized (I2PDActivity.class) { - 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); - menu.findItem(R.id.action_battery_otimizations).setVisible(isBatteryOptimizationsOpenOsDialogApiAvailable()); - this.optionsMenu = menu; - return true; - } - - private boolean isBatteryOptimizationsOpenOsDialogApiAvailable() { - return android.os.Build.VERSION.SDK_INT >= 23; - } - - @Override - public boolean onOptionsItemSelected(@NonNull 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_stop: - i2pdStop(); - return true; - - case R.id.action_graceful_stop: - synchronized (graceStartedMillis_LOCK) { - if (getGracefulQuitTimer() != null) - cancelGracefulStop(); - else - i2pdGracefulStop(); - } - return true; - - case R.id.action_battery_otimizations: - onActionBatteryOptimizations(); - return true; - - case R.id.action_reload_tunnels_config: - onReloadTunnelsConfig(); - return true; - - case R.id.action_start_webview: - setContentView(R.layout.webview); - this.webView = (WebView) findViewById(R.id.webview1); - this.webView.setWebViewClient(new WebViewClient()); - - WebSettings webSettings = this.webView.getSettings(); - webSettings.setBuiltInZoomControls(true); - webSettings.setJavaScriptEnabled(false); - this.webView.loadUrl("http://127.0.0.1:7070"); // TODO: instead 7070 I2Pd....HttpPort - break; - } - - return super.onOptionsItemSelected(item); - } - - private void onActionBatteryOptimizations() { - if (isBatteryOptimizationsOpenOsDialogApiAvailable()) { - try { - startActivity(new Intent(ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "BATT_OPTIM_DIALOG_ActvtNotFound", e); - Toast.makeText(this, R.string.os_version_does_not_support_battery_optimizations_show_os_dialog_api, Toast.LENGTH_SHORT).show(); - } - } - } - - private void onReloadTunnelsConfig() { - Log.d(TAG, "reloading tunnels"); - daemon.reloadTunnelsConfigs(); - Toast.makeText(this, R.string.tunnels_reloading, Toast.LENGTH_SHORT).show(); - } - - private void i2pdStop() { - cancelGracefulStop0(); - new Thread(() -> { - Log.d(TAG, "stopping"); - try { - daemon.stopDaemon(); - } catch (Throwable tr) { - Log.e(TAG, "", tr); - } - quit(); //TODO make menu items for starting i2pd. On my Android, I need to reboot the OS to restart i2pd. - }, "stop").start(); - } - - private static volatile Timer gracefulQuitTimer; - - private void i2pdGracefulStop() { - if (daemon.getState() == DaemonSingleton.State.stopped) { - Toast.makeText(this, R.string.already_stopped, Toast.LENGTH_SHORT).show(); - return; - } - if (getGracefulQuitTimer() != null) { - Toast.makeText(this, R.string.graceful_stop_is_already_in_progress, Toast.LENGTH_SHORT).show(); - return; - } - Toast.makeText(this, R.string.graceful_stop_is_in_progress, Toast.LENGTH_SHORT).show(); - new Thread(() -> { - try { - Log.d(TAG, "graceful stopping"); - if (daemon.isStartedOkay()) { - daemon.stopAcceptingTunnels(); - long gracefulStopAtMillis; - synchronized (graceStartedMillis_LOCK) { - graceStartedMillis = System.currentTimeMillis(); - gracefulStopAtMillis = graceStartedMillis + GRACEFUL_DELAY_MILLIS; - } - rescheduleGraceStop(null, gracefulStopAtMillis); - } else - i2pdStop(); - } catch(Throwable tr) { - Log.e(TAG, "", tr); - } - }, "gracInit").start(); - } - - private void cancelGracefulStop() - { - cancelGracefulStop0(); - new Thread(() -> { - try { - Log.d(TAG, "canceling graceful stop"); - if (daemon.isStartedOkay()) { - daemon.startAcceptingTunnels(); - runOnUiThread(() -> Toast.makeText(this, R.string.shutdown_canceled, Toast.LENGTH_SHORT).show()); - } else - i2pdStop(); - } catch(Throwable tr) { - Log.e(TAG, "", tr); - } - }, "gracCancel").start(); - } - - private void rescheduleGraceStop(Timer gracefulQuitTimerOld, long gracefulStopAtMillis) { - if (gracefulQuitTimerOld != null) - gracefulQuitTimerOld.cancel(); - - if(daemon.GetTransitTunnelsCount() <= 0) { // no tunnels left - Log.d(TAG, "no transit tunnels left, stopping"); - i2pdStop(); - } - - final Timer gracefulQuitTimer = new Timer(true); - setGracefulQuitTimer(gracefulQuitTimer); - gracefulQuitTimer.schedule(new TimerTask() { - - @Override - public void run() { - i2pdStop(); - } - - }, Math.max(0, gracefulStopAtMillis - System.currentTimeMillis())); - final TimerTask tickerTask = new TimerTask() { - @Override - public void run() { - daemonStateUpdatedListener.daemonStateUpdate(); - } - }; - gracefulQuitTimer.scheduleAtFixedRate(tickerTask, 0/*start delay*/, 1000/*millis period*/); - } - - private static Timer getGracefulQuitTimer() { - return gracefulQuitTimer; - } - - private void setGracefulQuitTimer(Timer gracefulQuitTimer) { - I2PDActivity.gracefulQuitTimer = gracefulQuitTimer; - runOnUiThread(()-> { - Menu menu = optionsMenu; - if (menu != null) { - MenuItem item = menu.findItem(R.id.action_graceful_stop); - if (item != null) { - synchronized (graceStartedMillis_LOCK) { - item.setTitle(getGracefulQuitTimer() != null ? R.string.action_cancel_graceful_stop : R.string.action_graceful_stop); - } - } - } - }); - } - - /** - * Copy the asset at the specified path to this app's data directory. If the - * asset is a directory, its contents are also copied. - * - * @param path - * Path to asset, relative to app's assets directory. - */ - private void copyAsset(String path) { - AssetManager manager = getAssets(); - - // If we have a directory, we make it and recurse. If a file, we copy its - // contents. - try { - String[] contents = manager.list(path); - - // The documentation suggests that list throws an IOException, but doesn't - // say under what conditions. It'd be nice if it did so when the path was - // to a file. That doesn't appear to be the case. If the returned array is - // null or has 0 length, we assume the path is to a file. This means empty - // directories will get turned into files. - if (contents == null || contents.length == 0) { - copyFileAsset(path); - return; - } - - // Make the directory. - File dir = new File(i2pdpath, path); - boolean result = dir.mkdirs(); - Log.d(TAG, "dir.mkdirs() returned " + result); - - // Recurse on the contents. - for (String entry : contents) { - copyAsset(path + '/' + entry); - } - } catch (IOException e) { - Log.e(TAG, "ex ignored for path='" + path + "'", e); - } - } - - /** - * Copy the asset file specified by path to app's data directory. Assumes - * parent directories have already been created. - * - * @param path - * Path to asset, relative to app's assets directory. - */ - private void copyFileAsset(String path) { - File file = new File(i2pdpath, path); - if (!file.exists()) { - try { - try (InputStream in = getAssets().open(path)) { - try (OutputStream out = new FileOutputStream(file)) { - byte[] buffer = new byte[1024]; - int read = in.read(buffer); - while (read != -1) { - out.write(buffer, 0, read); - read = in.read(buffer); - } - } - } - } catch (IOException e) { - Log.e(TAG, "", e); - } - } - } - - private void deleteRecursive(File fileOrDirectory) { - if (fileOrDirectory.isDirectory()) { - File[] files = fileOrDirectory.listFiles(); - if (files != null) { - for (File child : files) { - deleteRecursive(child); - } - } - } - boolean deleteResult = fileOrDirectory.delete(); - if (!deleteResult) - Log.e(TAG, "fileOrDirectory.delete() returned " + deleteResult + ", absolute path='" + fileOrDirectory.getAbsolutePath() + "'"); - } - - private void processAssets() { - if (!assetsCopied) { - try { - assetsCopied = true; // prevent from running on every state update - - File holderFile = new File(i2pdpath, "assets.ready"); - String versionName = BuildConfig.VERSION_NAME; // here will be app version, like 2.XX.XX - StringBuilder text = new StringBuilder(); - - if (holderFile.exists()) { - try { // if holder file exists, read assets version string - FileReader fileReader = new FileReader(holderFile); - - try { - BufferedReader br = new BufferedReader(fileReader); - - try { - String line; - - while ((line = br.readLine()) != null) { - text.append(line); - } - }finally { - try { - br.close(); - } catch (IOException e) { - Log.e(TAG, "", e); - } - } - } finally { - try { - fileReader.close(); - } catch (IOException e) { - Log.e(TAG, "", e); - } - } - } catch (IOException e) { - Log.e(TAG, "", e); - } - } - - // if version differs from current app version or null, try to delete certificates folder - if (!text.toString().contains(versionName)) - try { - boolean deleteResult = holderFile.delete(); - if (!deleteResult) - Log.e(TAG, "holderFile.delete() returned " + deleteResult + ", absolute path='" + holderFile.getAbsolutePath() + "'"); - File certPath = new File(i2pdpath, "certificates"); - deleteRecursive(certPath); - } - catch (Throwable tr) { - Log.e(TAG, "", tr); - } - - // copy assets. If processed file exists, it won't be overwritten - copyAsset("addressbook"); - copyAsset("certificates"); - copyAsset("tunnels.d"); - copyAsset("i2pd.conf"); - copyAsset("subscriptions.txt"); - copyAsset("tunnels.conf"); - - // update holder file about successful copying - FileWriter writer = new FileWriter(holderFile); - try { - writer.append(versionName); - } finally { - try { - writer.close(); - } catch (IOException e) { - Log.e(TAG,"on writer close", e); - } - } - } - catch (Throwable tr) - { - Log.e(TAG,"on assets copying", tr); - } - } - } - - @SuppressLint("BatteryLife") - private void openBatteryOptimizationDialogIfNeeded() { - boolean questionEnabled = getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true); - Log.i(TAG, "BATT_OPTIM_questionEnabled==" + questionEnabled); - if (!isKnownIgnoringBatteryOptimizations() - && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M - && questionEnabled) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(R.string.battery_optimizations_enabled); - builder.setMessage(R.string.battery_optimizations_enabled_dialog); - builder.setPositiveButton(R.string.continue_str, (dialog, which) -> { - try { - startActivity(new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Uri.parse(PACKAGE_URI_SCHEME + getPackageName()))); - } catch (ActivityNotFoundException e) { - Log.e(TAG, "BATT_OPTIM_ActvtNotFound", e); - Toast.makeText(this, R.string.device_does_not_support_disabling_battery_optimizations, Toast.LENGTH_SHORT).show(); - } - }); - builder.setOnDismissListener(dialog -> setNeverAskForBatteryOptimizationsAgain()); - final AlertDialog dialog = builder.create(); - dialog.setCanceledOnTouchOutside(false); - dialog.show(); - } - } - - private void setNeverAskForBatteryOptimizationsAgain() { - getPreferences().edit().putBoolean(getBatteryOptimizationPreferenceKey(), false).apply(); - } - - protected boolean isKnownIgnoringBatteryOptimizations() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - final PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); - if (pm == null) { - Log.i(TAG, "BATT_OPTIM: POWER_SERVICE==null"); - return false; - } - boolean ignoring = pm.isIgnoringBatteryOptimizations(getPackageName()); - Log.i(TAG, "BATT_OPTIM: ignoring==" + ignoring); - return ignoring; - } else { - Log.i(TAG, "BATT_OPTIM: old sdk version==" + Build.VERSION.SDK_INT); - return false; - } - } - - protected SharedPreferences getPreferences() { - return PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - } - - private String getBatteryOptimizationPreferenceKey() { - @SuppressLint("HardwareIds") String device = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID); - return "show_battery_optimization" + (device == null ? "" : device); - } - - private void quit() { - try { - 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); - } - try { - daemon.stopDaemon(); - } catch (Throwable tr) { - Log.e(TAG, "", tr); - } - System.exit(0); - } + private WebView webView; + + private static final String TAG = "i2pdActvt"; + private static final int MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 1; + public static final int GRACEFUL_DELAY_MILLIS = 10 * 60 * 1000; + public static final String PACKAGE_URI_SCHEME = "package:"; + + private TextView textView; + private boolean assetsCopied; + private String i2pdpath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/i2pd/"; + //private ConfigParser parser = new ConfigParser(i2pdpath); // TODO: + + private static final DaemonSingleton daemon = DaemonSingleton.getInstance(); + + private final DaemonSingleton.StateUpdateListener daemonStateUpdatedListener = new DaemonSingleton.StateUpdateListener() { + @Override + public void daemonStateUpdate() { + processAssets(); + runOnUiThread(() -> { + try { + if (textView == null) + return; + Throwable tr = daemon.getLastThrowable(); + if (tr != null) { + textView.setText(throwableToString(tr)); + return; + } + DaemonSingleton.State state = daemon.getState(); + String startResultStr = DaemonSingleton.State.startFailed.equals(state) ? String.format(": %s", daemon.getDaemonStartResult()) : ""; + String graceStr = DaemonSingleton.State.gracefulShutdownInProgress.equals(state) ? String.format(": %s %s", formatGraceTimeRemaining(), getText(R.string.remaining)) : ""; + textView.setText(String.format("%s%s%s", getText(state.getStatusStringResourceId()), startResultStr, graceStr)); + } catch (Throwable tr) { + Log.e(TAG, "error ignored", tr); + } + }); + } + }; + private static volatile long graceStartedMillis; + private static final Object graceStartedMillis_LOCK = new Object(); + private Menu optionsMenu; + + private static String formatGraceTimeRemaining() { + long remainingSeconds; + synchronized (graceStartedMillis_LOCK) { + remainingSeconds = Math.round(Math.max(0, graceStartedMillis + GRACEFUL_DELAY_MILLIS - System.currentTimeMillis()) / 1000.0D); + } + long remainingMinutes = (long) Math.floor(remainingSeconds / 60.0D); + long remSec = remainingSeconds - remainingMinutes * 60; + return remainingMinutes + ":" + (remSec / 10) + remSec % 10; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + Log.i(TAG, "onCreate"); + super.onCreate(savedInstanceState); + + textView = new TextView(this); + setContentView(textView); + daemon.addStateChangeListener(daemonStateUpdatedListener); + daemonStateUpdatedListener.daemonStateUpdate(); + + // request permissions + if (Build.VERSION.SDK_INT >= 23) { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE); + } + } + + // set the app be foreground + doBindService(); + + final Timer gracefulQuitTimer = getGracefulQuitTimer(); + if (gracefulQuitTimer != null) { + long gracefulStopAtMillis; + synchronized (graceStartedMillis_LOCK) { + gracefulStopAtMillis = graceStartedMillis + GRACEFUL_DELAY_MILLIS; + } + rescheduleGraceStop(gracefulQuitTimer, gracefulStopAtMillis); + } + + openBatteryOptimizationDialogIfNeeded(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + textView = null; + daemon.removeStateChangeListener(daemonStateUpdatedListener); + //cancelGracefulStop0(); + try { + doUnbindService(); + } catch (Throwable tr) { + Log.e(TAG, "", tr); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + if (requestCode == MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) + Log.e(TAG, "WR_EXT_STORAGE perm granted"); + else { + Log.e(TAG, "WR_EXT_STORAGE perm declined, stopping i2pd"); + i2pdStop(); + //TODO must work w/o this perm, ask orignal + } + } + } + + private void cancelGracefulStop0() { + Timer gracefulQuitTimer = getGracefulQuitTimer(); + if (gracefulQuitTimer != null) { + gracefulQuitTimer.cancel(); + setGracefulQuitTimer(null); + } + } + + private CharSequence throwableToString(Throwable tr) { + StringWriter sw = new StringWriter(8192); + PrintWriter pw = new PrintWriter(sw); + tr.printStackTrace(pw); + pw.close(); + return sw.toString(); + } + + // 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 static volatile boolean mIsBound; + + private void doBindService() { + synchronized (I2PDActivity.class) { + if (mIsBound) + return; + // 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() { + synchronized (I2PDActivity.class) { + 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); + menu.findItem(R.id.action_battery_otimizations).setVisible(isBatteryOptimizationsOpenOsDialogApiAvailable()); + this.optionsMenu = menu; + return true; + } + + private boolean isBatteryOptimizationsOpenOsDialogApiAvailable() { + return android.os.Build.VERSION.SDK_INT >= 23; + } + + @Override + public boolean onOptionsItemSelected(@NonNull 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_stop: + i2pdStop(); + return true; + + case R.id.action_graceful_stop: + synchronized (graceStartedMillis_LOCK) { + if (getGracefulQuitTimer() != null) + cancelGracefulStop(); + else + i2pdGracefulStop(); + } + return true; + + case R.id.action_battery_otimizations: + onActionBatteryOptimizations(); + return true; + + case R.id.action_reload_tunnels_config: + onReloadTunnelsConfig(); + return true; + + case R.id.action_start_webview: + setContentView(R.layout.webview); + this.webView = (WebView) findViewById(R.id.webview1); + this.webView.setWebViewClient(new WebViewClient()); + + WebSettings webSettings = this.webView.getSettings(); + webSettings.setBuiltInZoomControls(true); + webSettings.setJavaScriptEnabled(false); + this.webView.loadUrl("http://127.0.0.1:7070"); // TODO: instead 7070 I2Pd....HttpPort + break; + } + + return super.onOptionsItemSelected(item); + } + + private void onActionBatteryOptimizations() { + if (isBatteryOptimizationsOpenOsDialogApiAvailable()) { + try { + startActivity(new Intent(ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "BATT_OPTIM_DIALOG_ActvtNotFound", e); + Toast.makeText(this, R.string.os_version_does_not_support_battery_optimizations_show_os_dialog_api, Toast.LENGTH_SHORT).show(); + } + } + } + + private void onReloadTunnelsConfig() { + Log.d(TAG, "reloading tunnels"); + daemon.reloadTunnelsConfigs(); + Toast.makeText(this, R.string.tunnels_reloading, Toast.LENGTH_SHORT).show(); + } + + private void i2pdStop() { + cancelGracefulStop0(); + new Thread(() -> { + Log.d(TAG, "stopping"); + try { + daemon.stopDaemon(); + } catch (Throwable tr) { + Log.e(TAG, "", tr); + } + quit(); //TODO make menu items for starting i2pd. On my Android, I need to reboot the OS to restart i2pd. + }, "stop").start(); + } + + private static volatile Timer gracefulQuitTimer; + + private void i2pdGracefulStop() { + if (daemon.getState() == DaemonSingleton.State.stopped) { + Toast.makeText(this, R.string.already_stopped, Toast.LENGTH_SHORT).show(); + return; + } + if (getGracefulQuitTimer() != null) { + Toast.makeText(this, R.string.graceful_stop_is_already_in_progress, Toast.LENGTH_SHORT).show(); + return; + } + Toast.makeText(this, R.string.graceful_stop_is_in_progress, Toast.LENGTH_SHORT).show(); + new Thread(() -> { + try { + Log.d(TAG, "graceful stopping"); + if (daemon.isStartedOkay()) { + daemon.stopAcceptingTunnels(); + long gracefulStopAtMillis; + synchronized (graceStartedMillis_LOCK) { + graceStartedMillis = System.currentTimeMillis(); + gracefulStopAtMillis = graceStartedMillis + GRACEFUL_DELAY_MILLIS; + } + rescheduleGraceStop(null, gracefulStopAtMillis); + } else + i2pdStop(); + } catch (Throwable tr) { + Log.e(TAG, "", tr); + } + }, "gracInit").start(); + } + + private void cancelGracefulStop() { + cancelGracefulStop0(); + new Thread(() -> { + try { + Log.d(TAG, "canceling graceful stop"); + if (daemon.isStartedOkay()) { + daemon.startAcceptingTunnels(); + runOnUiThread(() -> Toast.makeText(this, R.string.shutdown_canceled, Toast.LENGTH_SHORT).show()); + } else + i2pdStop(); + } catch (Throwable tr) { + Log.e(TAG, "", tr); + } + }, "gracCancel").start(); + } + + private void rescheduleGraceStop(Timer gracefulQuitTimerOld, long gracefulStopAtMillis) { + if (gracefulQuitTimerOld != null) + gracefulQuitTimerOld.cancel(); + + if (daemon.GetTransitTunnelsCount() <= 0) { // no tunnels left + Log.d(TAG, "no transit tunnels left, stopping"); + i2pdStop(); + } + + final Timer gracefulQuitTimer = new Timer(true); + setGracefulQuitTimer(gracefulQuitTimer); + gracefulQuitTimer.schedule(new TimerTask() { + + @Override + public void run() { + i2pdStop(); + } + + }, Math.max(0, gracefulStopAtMillis - System.currentTimeMillis())); + final TimerTask tickerTask = new TimerTask() { + @Override + public void run() { + daemonStateUpdatedListener.daemonStateUpdate(); + } + }; + gracefulQuitTimer.scheduleAtFixedRate(tickerTask, 0/*start delay*/, 1000/*millis period*/); + } + + private static Timer getGracefulQuitTimer() { + return gracefulQuitTimer; + } + + private void setGracefulQuitTimer(Timer gracefulQuitTimer) { + I2PDActivity.gracefulQuitTimer = gracefulQuitTimer; + runOnUiThread(() -> { + Menu menu = optionsMenu; + if (menu != null) { + MenuItem item = menu.findItem(R.id.action_graceful_stop); + if (item != null) { + synchronized (graceStartedMillis_LOCK) { + item.setTitle(getGracefulQuitTimer() != null ? R.string.action_cancel_graceful_stop : R.string.action_graceful_stop); + } + } + } + }); + } + + /** + * Copy the asset at the specified path to this app's data directory. If the + * asset is a directory, its contents are also copied. + * + * @param path Path to asset, relative to app's assets directory. + */ + private void copyAsset(String path) { + AssetManager manager = getAssets(); + + // If we have a directory, we make it and recurse. If a file, we copy its + // contents. + try { + String[] contents = manager.list(path); + + // The documentation suggests that list throws an IOException, but doesn't + // say under what conditions. It'd be nice if it did so when the path was + // to a file. That doesn't appear to be the case. If the returned array is + // null or has 0 length, we assume the path is to a file. This means empty + // directories will get turned into files. + if (contents == null || contents.length == 0) { + copyFileAsset(path); + return; + } + + // Make the directory. + File dir = new File(i2pdpath, path); + boolean result = dir.mkdirs(); + Log.d(TAG, "dir.mkdirs() returned " + result); + + // Recurse on the contents. + for (String entry : contents) { + copyAsset(path + '/' + entry); + } + } catch (IOException e) { + Log.e(TAG, "ex ignored for path='" + path + "'", e); + } + } + + /** + * Copy the asset file specified by path to app's data directory. Assumes + * parent directories have already been created. + * + * @param path Path to asset, relative to app's assets directory. + */ + private void copyFileAsset(String path) { + File file = new File(i2pdpath, path); + if (!file.exists()) { + try { + try (InputStream in = getAssets().open(path)) { + try (OutputStream out = new FileOutputStream(file)) { + byte[] buffer = new byte[1024]; + int read = in.read(buffer); + while (read != -1) { + out.write(buffer, 0, read); + read = in.read(buffer); + } + } + } + } catch (IOException e) { + Log.e(TAG, "", e); + } + } + } + + private void deleteRecursive(File fileOrDirectory) { + if (fileOrDirectory.isDirectory()) { + File[] files = fileOrDirectory.listFiles(); + if (files != null) { + for (File child : files) { + deleteRecursive(child); + } + } + } + boolean deleteResult = fileOrDirectory.delete(); + if (!deleteResult) + Log.e(TAG, "fileOrDirectory.delete() returned " + deleteResult + ", absolute path='" + fileOrDirectory.getAbsolutePath() + "'"); + } + + private void processAssets() { + if (!assetsCopied) { + try { + assetsCopied = true; // prevent from running on every state update + + File holderFile = new File(i2pdpath, "assets.ready"); + String versionName = BuildConfig.VERSION_NAME; // here will be app version, like 2.XX.XX + StringBuilder text = new StringBuilder(); + + if (holderFile.exists()) { + try { // if holder file exists, read assets version string + FileReader fileReader = new FileReader(holderFile); + + try { + BufferedReader br = new BufferedReader(fileReader); + + try { + String line; + + while ((line = br.readLine()) != null) { + text.append(line); + } + } finally { + try { + br.close(); + } catch (IOException e) { + Log.e(TAG, "", e); + } + } + } finally { + try { + fileReader.close(); + } catch (IOException e) { + Log.e(TAG, "", e); + } + } + } catch (IOException e) { + Log.e(TAG, "", e); + } + } + + // if version differs from current app version or null, try to delete certificates folder + if (!text.toString().contains(versionName)) + try { + boolean deleteResult = holderFile.delete(); + if (!deleteResult) + Log.e(TAG, "holderFile.delete() returned " + deleteResult + ", absolute path='" + holderFile.getAbsolutePath() + "'"); + File certPath = new File(i2pdpath, "certificates"); + deleteRecursive(certPath); + } catch (Throwable tr) { + Log.e(TAG, "", tr); + } + + // copy assets. If processed file exists, it won't be overwritten + copyAsset("addressbook"); + copyAsset("certificates"); + copyAsset("tunnels.d"); + copyAsset("i2pd.conf"); + copyAsset("subscriptions.txt"); + copyAsset("tunnels.conf"); + + // update holder file about successful copying + FileWriter writer = new FileWriter(holderFile); + try { + writer.append(versionName); + } finally { + try { + writer.close(); + } catch (IOException e) { + Log.e(TAG, "on writer close", e); + } + } + } catch (Throwable tr) { + Log.e(TAG, "on assets copying", tr); + } + } + } + + @SuppressLint("BatteryLife") + private void openBatteryOptimizationDialogIfNeeded() { + boolean questionEnabled = getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true); + Log.i(TAG, "BATT_OPTIM_questionEnabled==" + questionEnabled); + if (!isKnownIgnoringBatteryOptimizations() + && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M + && questionEnabled) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.battery_optimizations_enabled); + builder.setMessage(R.string.battery_optimizations_enabled_dialog); + builder.setPositiveButton(R.string.continue_str, (dialog, which) -> { + try { + startActivity(new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS, Uri.parse(PACKAGE_URI_SCHEME + getPackageName()))); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "BATT_OPTIM_ActvtNotFound", e); + Toast.makeText(this, R.string.device_does_not_support_disabling_battery_optimizations, Toast.LENGTH_SHORT).show(); + } + }); + builder.setOnDismissListener(dialog -> setNeverAskForBatteryOptimizationsAgain()); + final AlertDialog dialog = builder.create(); + dialog.setCanceledOnTouchOutside(false); + dialog.show(); + } + } + + private void setNeverAskForBatteryOptimizationsAgain() { + getPreferences().edit().putBoolean(getBatteryOptimizationPreferenceKey(), false).apply(); + } + + protected boolean isKnownIgnoringBatteryOptimizations() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + final PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); + if (pm == null) { + Log.i(TAG, "BATT_OPTIM: POWER_SERVICE==null"); + return false; + } + boolean ignoring = pm.isIgnoringBatteryOptimizations(getPackageName()); + Log.i(TAG, "BATT_OPTIM: ignoring==" + ignoring); + return ignoring; + } else { + Log.i(TAG, "BATT_OPTIM: old sdk version==" + Build.VERSION.SDK_INT); + return false; + } + } + + protected SharedPreferences getPreferences() { + return PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + } + + private String getBatteryOptimizationPreferenceKey() { + @SuppressLint("HardwareIds") String device = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID); + return "show_battery_optimization" + (device == null ? "" : device); + } + + private void quit() { + try { + 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); + } + try { + daemon.stopDaemon(); + } catch (Throwable tr) { + Log.e(TAG, "", tr); + } + System.exit(0); + } } diff --git a/app/src/main/java/org/purplei2p/i2pd/I2PDPermsAskerActivity.java b/app/src/main/java/org/purplei2p/i2pd/I2PDPermsAskerActivity.java index 149a3aa..dd54e79 100644 --- a/app/src/main/java/org/purplei2p/i2pd/I2PDPermsAskerActivity.java +++ b/app/src/main/java/org/purplei2p/i2pd/I2PDPermsAskerActivity.java @@ -15,156 +15,157 @@ import java.lang.reflect.Method; //android.permission.WRITE_EXTERNAL_STORAGE public class I2PDPermsAskerActivity extends Activity { - private static final int PERMISSION_WRITE_EXTERNAL_STORAGE = 0; - - private Button button_request_write_ext_storage_perms; - private TextView textview_retry; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - //if less than Android 6, no runtime perms req system present - if (android.os.Build.VERSION.SDK_INT < 23) { - startMainActivity(); - return; - } - - - setContentView(R.layout.activity_perms_asker); - button_request_write_ext_storage_perms = (Button) findViewById(R.id.button_request_write_ext_storage_perms); - textview_retry = (TextView) findViewById(R.id.textview_retry); - - button_request_write_ext_storage_perms.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - request_write_ext_storage_perms(); - } - }); - request_write_ext_storage_perms(); - } - - private void request_write_ext_storage_perms() { - - textview_retry.setVisibility(TextView.GONE); - button_request_write_ext_storage_perms.setVisibility(Button.GONE); - - Method methodCheckPermission; - Method method_shouldShowRequestPermissionRationale; - Method method_requestPermissions; - try { - methodCheckPermission = getClass().getMethod("checkSelfPermission", String.class); - method_shouldShowRequestPermissionRationale = - getClass().getMethod("shouldShowRequestPermissionRationale", String.class); - method_requestPermissions = - getClass().getMethod("requestPermissions", String[].class, int.class); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - Integer resultObj; - try { - resultObj = (Integer) methodCheckPermission.invoke( - this, Manifest.permission.WRITE_EXTERNAL_STORAGE); - } catch (Throwable e) { - throw new RuntimeException(e); - } - - if (resultObj != PackageManager.PERMISSION_GRANTED) { - - // Should we show an explanation? - Boolean aBoolean; - try { - aBoolean = (Boolean) method_shouldShowRequestPermissionRationale.invoke(this, - Manifest.permission.WRITE_EXTERNAL_STORAGE); - } catch (Exception e) { - throw new RuntimeException(e); - } - if (aBoolean) { - - // Show an explanation to the user *asynchronously* -- don't block - // this thread waiting for the user's response! After the user - // sees the explanation, try again to request the permission. - - showExplanation(); - - } else { - - // No explanation needed, we can request the permission. - - try { - method_requestPermissions.invoke(this, - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSION_WRITE_EXTERNAL_STORAGE); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } else startMainActivity(); - } - - @Override - public void onRequestPermissionsResult(int requestCode, - String permissions[], int[] grantResults) { - switch (requestCode) { - case PERMISSION_WRITE_EXTERNAL_STORAGE: { - // If request is cancelled, the result arrays are empty. - if (grantResults.length > 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - - // permission was granted, yay! Do the - // contacts-related task you need to do. - - startMainActivity(); - - } else { - - // permission denied, boo! Disable the - // functionality that depends on this permission. - textview_retry.setText(R.string.permDenied); - textview_retry.setVisibility(TextView.VISIBLE); - button_request_write_ext_storage_perms.setVisibility(Button.VISIBLE); - } - } - - // other 'case' lines to check for other - // permissions this app might request. - } - } - - private void startMainActivity() { - startActivity(new Intent(this, I2PDActivity.class)); - finish(); - } - - private static final int SHOW_EXPLANATION_REQUEST = 1; // The request code - private void showExplanation() { - Intent intent = new Intent(this, I2PDPermsExplanationActivity.class); - startActivityForResult(intent, SHOW_EXPLANATION_REQUEST); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - // Check which request we're responding to - if (requestCode == SHOW_EXPLANATION_REQUEST) { - // Make sure the request was successful - if (resultCode == RESULT_OK) { - // Request the permission - Method method_requestPermissions; - try { - method_requestPermissions = - getClass().getMethod("requestPermissions", String[].class, int.class); - } catch (NoSuchMethodException e) { - throw new RuntimeException(e); - } - try { - method_requestPermissions.invoke(this, - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSION_WRITE_EXTERNAL_STORAGE); - } catch (Exception e) { - throw new RuntimeException(e); - } - } else { - finish(); //close the app - } - } - } + private static final int PERMISSION_WRITE_EXTERNAL_STORAGE = 0; + + private Button button_request_write_ext_storage_perms; + private TextView textview_retry; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + //if less than Android 6, no runtime perms req system present + if (android.os.Build.VERSION.SDK_INT < 23) { + startMainActivity(); + return; + } + + + setContentView(R.layout.activity_perms_asker); + button_request_write_ext_storage_perms = (Button) findViewById(R.id.button_request_write_ext_storage_perms); + textview_retry = (TextView) findViewById(R.id.textview_retry); + + button_request_write_ext_storage_perms.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + request_write_ext_storage_perms(); + } + }); + request_write_ext_storage_perms(); + } + + private void request_write_ext_storage_perms() { + + textview_retry.setVisibility(TextView.GONE); + button_request_write_ext_storage_perms.setVisibility(Button.GONE); + + Method methodCheckPermission; + Method method_shouldShowRequestPermissionRationale; + Method method_requestPermissions; + try { + methodCheckPermission = getClass().getMethod("checkSelfPermission", String.class); + method_shouldShowRequestPermissionRationale = + getClass().getMethod("shouldShowRequestPermissionRationale", String.class); + method_requestPermissions = + getClass().getMethod("requestPermissions", String[].class, int.class); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + Integer resultObj; + try { + resultObj = (Integer) methodCheckPermission.invoke( + this, Manifest.permission.WRITE_EXTERNAL_STORAGE); + } catch (Throwable e) { + throw new RuntimeException(e); + } + + if (resultObj != PackageManager.PERMISSION_GRANTED) { + + // Should we show an explanation? + Boolean aBoolean; + try { + aBoolean = (Boolean) method_shouldShowRequestPermissionRationale.invoke(this, + Manifest.permission.WRITE_EXTERNAL_STORAGE); + } catch (Exception e) { + throw new RuntimeException(e); + } + if (aBoolean) { + + // Show an explanation to the user *asynchronously* -- don't block + // this thread waiting for the user's response! After the user + // sees the explanation, try again to request the permission. + + showExplanation(); + + } else { + + // No explanation needed, we can request the permission. + + try { + method_requestPermissions.invoke(this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + PERMISSION_WRITE_EXTERNAL_STORAGE); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } else startMainActivity(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, + String permissions[], int[] grantResults) { + switch (requestCode) { + case PERMISSION_WRITE_EXTERNAL_STORAGE: { + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 + && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + + // permission was granted, yay! Do the + // contacts-related task you need to do. + + startMainActivity(); + + } else { + + // permission denied, boo! Disable the + // functionality that depends on this permission. + textview_retry.setText(R.string.permDenied); + textview_retry.setVisibility(TextView.VISIBLE); + button_request_write_ext_storage_perms.setVisibility(Button.VISIBLE); + } + } + + // other 'case' lines to check for other + // permissions this app might request. + } + } + + private void startMainActivity() { + startActivity(new Intent(this, I2PDActivity.class)); + finish(); + } + + private static final int SHOW_EXPLANATION_REQUEST = 1; // The request code + + private void showExplanation() { + Intent intent = new Intent(this, I2PDPermsExplanationActivity.class); + startActivityForResult(intent, SHOW_EXPLANATION_REQUEST); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // Check which request we're responding to + if (requestCode == SHOW_EXPLANATION_REQUEST) { + // Make sure the request was successful + if (resultCode == RESULT_OK) { + // Request the permission + Method method_requestPermissions; + try { + method_requestPermissions = + getClass().getMethod("requestPermissions", String[].class, int.class); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + try { + method_requestPermissions.invoke(this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + PERMISSION_WRITE_EXTERNAL_STORAGE); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else { + finish(); //close the app + } + } + } } diff --git a/app/src/main/java/org/purplei2p/i2pd/I2PDPermsExplanationActivity.java b/app/src/main/java/org/purplei2p/i2pd/I2PDPermsExplanationActivity.java index 885df75..33f3a28 100644 --- a/app/src/main/java/org/purplei2p/i2pd/I2PDPermsExplanationActivity.java +++ b/app/src/main/java/org/purplei2p/i2pd/I2PDPermsExplanationActivity.java @@ -9,30 +9,30 @@ import android.widget.Button; public class I2PDPermsExplanationActivity extends Activity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_perms_explanation); - ActionBar actionBar = getActionBar(); - if(actionBar!=null)actionBar.setHomeButtonEnabled(false); - Button button_ok = (Button) findViewById(R.id.button_ok); - button_ok.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - returnFromActivity(); - } - }); - } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_perms_explanation); + ActionBar actionBar = getActionBar(); + if (actionBar != null) actionBar.setHomeButtonEnabled(false); + Button button_ok = (Button) findViewById(R.id.button_ok); + button_ok.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + returnFromActivity(); + } + }); + } - private void returnFromActivity() { - Intent data = new Intent(); - Activity parent = getParent(); - if (parent == null) { - setResult(Activity.RESULT_OK, data); - } else { - parent.setResult(Activity.RESULT_OK, data); - } - finish(); - } + private void returnFromActivity() { + Intent data = new Intent(); + Activity parent = getParent(); + if (parent == null) { + setResult(Activity.RESULT_OK, data); + } else { + parent.setResult(Activity.RESULT_OK, data); + } + finish(); + } } diff --git a/app/src/main/java/org/purplei2p/i2pd/I2PD_JNI.java b/app/src/main/java/org/purplei2p/i2pd/I2PD_JNI.java index 582b102..6d03c36 100644 --- a/app/src/main/java/org/purplei2p/i2pd/I2PD_JNI.java +++ b/app/src/main/java/org/purplei2p/i2pd/I2PD_JNI.java @@ -1,31 +1,31 @@ package org.purplei2p.i2pd; public class I2PD_JNI { - public static native String getABICompiledWith(); + public static native String getABICompiledWith(); - /** - * returns error info if failed - * returns "ok" if daemon initialized and started okay - */ - public static native String startDaemon(); + /** + * returns error info if failed + * returns "ok" if daemon initialized and started okay + */ + public static native String startDaemon(); - //should only be called after startDaemon() success - public static native void stopDaemon(); + //should only be called after startDaemon() success + public static native void stopDaemon(); - public static native void stopAcceptingTunnels(); + public static native void stopAcceptingTunnels(); - public static native void startAcceptingTunnels(); + public static native void startAcceptingTunnels(); - public static native void reloadTunnelsConfigs(); + public static native void reloadTunnelsConfigs(); - public static native void onNetworkStateChanged(boolean isConnected); + public static native void onNetworkStateChanged(boolean isConnected); - public static native void setDataDir(String jdataDir); + public static native void setDataDir(String jdataDir); - public static native int GetTransitTunnelsCount(); + public static native int GetTransitTunnelsCount(); - public static void loadLibraries() { - //System.loadLibrary("c++_shared"); - System.loadLibrary("i2pd"); - } + public static void loadLibraries() { + //System.loadLibrary("c++_shared"); + System.loadLibrary("i2pd"); + } } diff --git a/app/src/main/java/org/purplei2p/i2pd/NetworkStateChangeReceiver.java b/app/src/main/java/org/purplei2p/i2pd/NetworkStateChangeReceiver.java index 437debe..7699677 100644 --- a/app/src/main/java/org/purplei2p/i2pd/NetworkStateChangeReceiver.java +++ b/app/src/main/java/org/purplei2p/i2pd/NetworkStateChangeReceiver.java @@ -9,22 +9,23 @@ import android.net.NetworkInfo; public class NetworkStateChangeReceiver extends BroadcastReceiver { - private static final String TAG = "i2pd"; + 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); + //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); + assert cm != null; + 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); - } - } + I2PD_JNI.onNetworkStateChanged(isConnected); + } catch (Throwable tr) { + Log.d(TAG, "", tr); + } + } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b3b7e38..52161ef 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -20,7 +20,7 @@ Start failed Application stopped remaining - OK + OK Prompt SD card write permission denied, you need to allow this to continue diff --git a/build.gradle b/build.gradle index 1a87775..6b1a2cf 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.2' + classpath 'com.android.tools.build:gradle:3.6.1' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3b80321..17cbf82 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue Aug 20 14:39:08 MSK 2019 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip +#Fri Jun 12 07:37:19 MSK 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip diff --git a/settings.gradle b/settings.gradle index e7b4def..116ff4f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,2 @@ include ':app' +project(":app").name = "i2pd" \ No newline at end of file