@ -14,24 +14,34 @@ import java.util.Timer;
import java.util.TimerTask ;
import java.util.TimerTask ;
import android.Manifest ;
import android.Manifest ;
import android.annotation.SuppressLint ;
import android.app.Activity ;
import android.app.Activity ;
import android.app.AlertDialog ;
import android.content.ActivityNotFoundException ;
import android.content.ComponentName ;
import android.content.ComponentName ;
import android.content.Context ;
import android.content.Context ;
import android.content.Intent ;
import android.content.Intent ;
import android.content.ServiceConnection ;
import android.content.ServiceConnection ;
import android.content.SharedPreferences ;
import android.content.res.AssetManager ;
import android.content.res.AssetManager ;
import android.content.pm.PackageManager ;
import android.content.pm.PackageManager ;
import android.net.Uri ;
import android.os.Bundle ;
import android.os.Bundle ;
import android.os.Build ;
import android.os.Build ;
import android.os.Environment ;
import android.os.Environment ;
import android.os.IBinder ;
import android.os.IBinder ;
import android.os.PowerManager ;
import android.preference.PreferenceManager ;
import android.provider.Settings ;
import android.util.Log ;
import android.util.Log ;
import android.view.Menu ;
import android.view.Menu ;
import android.view.MenuItem ;
import android.view.MenuItem ;
import android.widget.TextView ;
import android.widget.TextView ;
import android.widget.Toast ;
import android.widget.Toast ;
import android.support.v4.app.ActivityCompat ;
import android.support.v4.content.ContextCompat ;
import androidx.annotation.NonNull ;
import androidx.core.app.ActivityCompat ;
import androidx.core.content.ContextCompat ;
// For future package update checking
// For future package update checking
import org.purplei2p.i2pd.BuildConfig ;
import org.purplei2p.i2pd.BuildConfig ;
@ -40,6 +50,7 @@ public class I2PDActivity extends Activity {
private static final String TAG = "i2pdActvt" ;
private static final String TAG = "i2pdActvt" ;
private static final int MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 1 ;
private static final int MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE = 1 ;
public static final int GRACEFUL_DELAY_MILLIS = 10 * 60 * 1000 ;
public static final int GRACEFUL_DELAY_MILLIS = 10 * 60 * 1000 ;
public static final String PACKAGE_URI_SCHEME = "package:" ;
private TextView textView ;
private TextView textView ;
private boolean assetsCopied ;
private boolean assetsCopied ;
@ -53,28 +64,22 @@ public class I2PDActivity extends Activity {
public void daemonStateUpdate ( )
public void daemonStateUpdate ( )
{
{
processAssets ( ) ;
processAssets ( ) ;
runOnUiThread ( new Runnable ( ) {
runOnUiThread ( ( ) - > {
try {
@Override
if ( textView = = null ) return ;
public void run ( ) {
Throwable tr = daemon . getLastThrowable ( ) ;
try {
if ( tr ! = null ) {
if ( textView = = null ) return ;
textView . setText ( throwableToString ( tr ) ) ;
Throwable tr = daemon . getLastThrowable ( ) ;
return ;
if ( tr ! = null ) {
}
textView . setText ( throwableToString ( tr ) ) ;
DaemonSingleton . State state = daemon . getState ( ) ;
return ;
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 ) ) : "" ;
DaemonSingleton . State state = daemon . getState ( ) ;
textView . setText ( String . format ( "%s%s%s" , getText ( state . getStatusStringResourceId ( ) ) , startResultStr , graceStr ) ) ;
textView . setText (
} catch ( Throwable tr ) {
String . valueOf ( getText ( state . getStatusStringResourceId ( ) ) ) +
Log . e ( TAG , "error ignored" , tr ) ;
( DaemonSingleton . State . startFailed . equals ( state ) ? ": " + daemon . getDaemonStartResult ( ) : "" ) +
}
( DaemonSingleton . State . gracefulShutdownInProgress . equals ( state ) ? ": " + formatGraceTimeRemaining ( ) + " " + getText ( R . string . remaining ) : "" )
} ) ;
) ;
} catch ( Throwable tr ) {
Log . e ( TAG , "error ignored" , tr ) ;
}
}
} ) ;
}
}
} ;
} ;
private static volatile long graceStartedMillis ;
private static volatile long graceStartedMillis ;
@ -92,6 +97,7 @@ public class I2PDActivity extends Activity {
@Override
@Override
public void onCreate ( Bundle savedInstanceState ) {
public void onCreate ( Bundle savedInstanceState ) {
Log . i ( TAG , "onCreate" ) ;
super . onCreate ( savedInstanceState ) ;
super . onCreate ( savedInstanceState ) ;
textView = new TextView ( this ) ;
textView = new TextView ( this ) ;
@ -121,6 +127,8 @@ public class I2PDActivity extends Activity {
}
}
rescheduleGraceStop ( gracefulQuitTimer , gracefulStopAtMillis ) ;
rescheduleGraceStop ( gracefulQuitTimer , gracefulStopAtMillis ) ;
}
}
openBatteryOptimizationDialogIfNeeded ( ) ;
}
}
@Override
@Override
@ -137,21 +145,17 @@ public class I2PDActivity extends Activity {
}
}
@Override
@Override
public void onRequestPermissionsResult ( int requestCode , String permissions [ ] , int [ ] grantResults )
public void onRequestPermissionsResult ( int requestCode , @NonNull String [ ] permissions , @NonNull int [ ] grantResults )
{
{
switch ( requestCode )
if ( requestCode = = MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE ) {
{
if ( grantResults . length > 0 & & grantResults [ 0 ] = = PackageManager . PERMISSION_GRANTED )
case MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE :
Log . e ( TAG , "WR_EXT_STORAGE perm granted" ) ;
{
else {
if ( grantResults . length > 0 & & grantResults [ 0 ] = = PackageManager . PERMISSION_GRANTED )
Log . e ( TAG , "WR_EXT_STORAGE perm declined, stopping i2pd" ) ;
Log . e ( TAG , "Memory permission granted" ) ;
i2pdStop ( ) ;
else
//TODO must work w/o this perm, ask orignal
Log . e ( TAG , "Memory permission declined" ) ;
}
// TODO: terminate
}
return ;
}
default : ;
}
}
}
private static void cancelGracefulStop ( ) {
private static void cancelGracefulStop ( ) {
@ -229,7 +233,7 @@ public class I2PDActivity extends Activity {
}
}
@Override
@Override
public boolean onOptionsItemSelected ( MenuItem item ) {
public boolean onOptionsItemSelected ( @NonNull MenuItem item ) {
// Handle action bar item clicks here. The action bar will
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
// as you specify a parent activity in AndroidManifest.xml.
@ -258,19 +262,15 @@ public class I2PDActivity extends Activity {
private void i2pdStop ( ) {
private void i2pdStop ( ) {
cancelGracefulStop ( ) ;
cancelGracefulStop ( ) ;
new Thread ( new Runnable ( ) {
new Thread ( ( ) - > {
Log . d ( TAG , "stopping" ) ;
@Override
try {
public void run ( ) {
daemon . stopDaemon ( ) ;
Log . d ( TAG , "stopping" ) ;
} catch ( Throwable tr ) {
try {
Log . e ( TAG , "" , tr ) ;
daemon . stopDaemon ( ) ;
}
} catch ( Throwable tr ) {
quit ( ) ; //TODO make menu items for starting i2pd. On my Android, I need to reboot the OS to restart i2pd.
Log . e ( TAG , "" , tr ) ;
} , "stop" ) . start ( ) ;
}
}
} , "stop" ) . start ( ) ;
}
}
private static volatile Timer gracefulQuitTimer ;
private static volatile Timer gracefulQuitTimer ;
@ -288,55 +288,44 @@ public class I2PDActivity extends Activity {
}
}
Toast . makeText ( this , R . string . graceful_stop_is_in_progress ,
Toast . makeText ( this , R . string . graceful_stop_is_in_progress ,
Toast . LENGTH_SHORT ) . show ( ) ;
Toast . LENGTH_SHORT ) . show ( ) ;
new Thread ( new Runnable ( ) {
new Thread ( ( ) - > {
try {
@Override
Log . d ( TAG , "grac stopping" ) ;
public void run ( ) {
if ( daemon . isStartedOkay ( ) ) {
try {
daemon . stopAcceptingTunnels ( ) ;
Log . d ( TAG , "grac stopping" ) ;
long gracefulStopAtMillis ;
if ( daemon . isStartedOkay ( ) ) {
synchronized ( graceStartedMillis_LOCK ) {
daemon . stopAcceptingTunnels ( ) ;
graceStartedMillis = System . currentTimeMillis ( ) ;
long gracefulStopAtMillis ;
gracefulStopAtMillis = graceStartedMillis + GRACEFUL_DELAY_MILLIS ;
synchronized ( graceStartedMillis_LOCK ) {
}
graceStartedMillis = System . currentTimeMillis ( ) ;
rescheduleGraceStop ( null , gracefulStopAtMillis ) ;
gracefulStopAtMillis = graceStartedMillis + GRACEFUL_DELAY_MILLIS ;
} else {
}
i2pdStop ( ) ;
rescheduleGraceStop ( null , gracefulStopAtMillis ) ;
}
} else {
} catch ( Throwable tr ) {
i2pdStop ( ) ;
Log . e ( TAG , "" , tr ) ;
}
}
} catch ( Throwable tr ) {
} , "gracInit" ) . start ( ) ;
Log . e ( TAG , "" , tr ) ;
}
}
} , "gracInit" ) . start ( ) ;
}
}
private void i2pdCancelGracefulStop ( )
private void i2pdCancelGracefulStop ( )
{
{
cancelGracefulStop ( ) ;
cancelGracefulStop ( ) ;
Toast . makeText ( this , R . string . startedOkay , Toast . LENGTH_SHORT ) . show ( ) ;
Toast . makeText ( this , R . string . startedOkay , Toast . LENGTH_SHORT ) . show ( ) ;
new Thread ( new Runnable ( )
new Thread ( ( ) - > {
{
try
@Override
{
public void run ( )
Log . d ( TAG , "grac stopping cancel" ) ;
{
if ( daemon . isStartedOkay ( ) )
try
daemon . startAcceptingTunnels ( ) ;
{
else
Log . d ( TAG , "grac stopping cancel" ) ;
i2pdStop ( ) ;
if ( daemon . isStartedOkay ( ) )
}
daemon . startAcceptingTunnels ( ) ;
catch ( Throwable tr )
else
{
i2pdStop ( ) ;
Log . e ( TAG , "" , tr ) ;
}
}
catch ( Throwable tr )
} , "gracCancel" ) . start ( ) ;
{
Log . e ( TAG , "" , tr ) ;
}
}
} , "gracCancel" ) . start ( ) ;
}
}
private void rescheduleGraceStop ( Timer gracefulQuitTimerOld , long gracefulStopAtMillis ) {
private void rescheduleGraceStop ( Timer gracefulQuitTimerOld , long gracefulStopAtMillis ) {
@ -393,7 +382,7 @@ public class I2PDActivity extends Activity {
// Make the directory.
// Make the directory.
File dir = new File ( i2pdpath , path ) ;
File dir = new File ( i2pdpath , path ) ;
dir . mkdirs ( ) ;
Log . d ( TAG , "dir.mkdirs() returned " + dir . mkdirs ( ) ) ;
// Recurse on the contents.
// Recurse on the contents.
for ( String entry : contents ) {
for ( String entry : contents ) {
@ -431,45 +420,69 @@ public class I2PDActivity extends Activity {
private void deleteRecursive ( File fileOrDirectory ) {
private void deleteRecursive ( File fileOrDirectory ) {
if ( fileOrDirectory . isDirectory ( ) ) {
if ( fileOrDirectory . isDirectory ( ) ) {
for ( File child : fileOrDirectory . listFiles ( ) ) {
File [ ] files = fileOrDirectory . listFiles ( ) ;
deleteRecursive ( child ) ;
if ( files ! = null ) {
for ( File child : files ) {
deleteRecursive ( child ) ;
}
}
}
}
}
fileOrDirectory . delete ( ) ;
boolean deleteResult = fileOrDirectory . delete ( ) ;
if ( ! deleteResult ) Log . e ( TAG , "fileOrDirectory.delete() returned " + deleteResult + ", absolute path='" + fileOrDirectory . getAbsolutePath ( ) + "'" ) ;
}
}
private void processAssets ( ) {
private void processAssets ( ) {
if ( ! assetsCopied ) try {
if ( ! assetsCopied ) try {
assetsCopied = true ; // prevent from running on every state update
assetsCopied = true ; // prevent from running on every state update
File holderf ile = new File ( i2pdpath , "assets.ready" ) ;
File holderF ile = new File ( i2pdpath , "assets.ready" ) ;
String versionName = BuildConfig . VERSION_NAME ; // here will be app version, like 2.XX.XX
String versionName = BuildConfig . VERSION_NAME ; // here will be app version, like 2.XX.XX
StringBuilder text = new StringBuilder ( ) ;
StringBuilder text = new StringBuilder ( ) ;
if ( holderfile . exists ( ) ) try { // if holder file exists, read assets version string
if ( holderFile . exists ( ) ) {
BufferedReader br = new BufferedReader ( new FileReader ( holderfile ) ) ;
try { // if holder file exists, read assets version string
String line ;
FileReader fileReader = new FileReader ( holderFile ) ;
while ( ( line = br . readLine ( ) ) ! = null ) {
try {
text . append ( line ) ;
BufferedReader br = new BufferedReader ( fileReader ) ;
}
br . close ( ) ;
try {
}
String line ;
catch ( IOException e ) {
Log . e ( TAG , "" , e ) ;
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 version differs from current app version or null, try to delete certificates folder
if ( ! text . toString ( ) . contains ( versionName ) ) try {
if ( ! text . toString ( ) . contains ( versionName ) ) try {
holderfile . delete ( ) ;
boolean deleteResult = holderFile . delete ( ) ;
File certpath = new File ( i2pdpath , "certificates" ) ;
if ( ! deleteResult ) Log . e ( TAG , "holderFile.delete() returned " + deleteResult + ", absolute path='" + holderFile . getAbsolutePath ( ) + "'" ) ;
deleteRecursive ( certpath ) ;
File certPath = new File ( i2pdpath , "certificates" ) ;
deleteRecursive ( certPath ) ;
}
}
catch ( Throwable tr ) {
catch ( Throwable tr ) {
Log . e ( TAG , "" , tr ) ;
Log . e ( TAG , "" , tr ) ;
}
}
// copy assets. If processed file exists, it won't be overwrited
// copy assets. If processed file exists, it won't be overwritten
copyAsset ( "addressbook" ) ;
copyAsset ( "addressbook" ) ;
copyAsset ( "certificates" ) ;
copyAsset ( "certificates" ) ;
copyAsset ( "tunnels.d" ) ;
copyAsset ( "tunnels.d" ) ;
@ -478,14 +491,95 @@ public class I2PDActivity extends Activity {
copyAsset ( "tunnels.conf" ) ;
copyAsset ( "tunnels.conf" ) ;
// update holder file about successful copying
// update holder file about successful copying
FileWriter writer = new FileWriter ( holderfile ) ;
FileWriter writer = new FileWriter ( holderFile ) ;
writer . append ( versionName ) ;
try {
writer . flush ( ) ;
writer . append ( versionName ) ;
writer . close ( ) ;
} finally {
try {
writer . close ( ) ;
} catch ( IOException e ) {
Log . e ( TAG , "on writer close" , e ) ;
}
}
}
}
catch ( Throwable tr )
catch ( Throwable tr )
{
{
Log . e ( TAG , "copy assets" , 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 . next , ( 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 ) ;
}
}
}
}