Browse Source

various Android stuff. Fixed #1400

pull/1405/head
kote 5 years ago
parent
commit
8f82d563c1
  1. 3
      android/.gitignore
  2. 1
      android/AndroidManifest.xml
  3. 13
      android/build.gradle
  4. 2
      android/gradle.properties
  5. 4
      android/gradle/wrapper/gradle-wrapper.properties
  6. 5
      android/res/values/strings.xml
  7. 20
      android/src/org/purplei2p/i2pd/ForegroundService.java
  8. 208
      android/src/org/purplei2p/i2pd/I2PDActivity.java

3
android/.gitignore vendored

@ -12,5 +12,4 @@ local.properties
build.sh build.sh
android.iml android.iml
build build
*.iml

1
android/AndroidManifest.xml

@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<application <application
android:allowBackup="true" android:allowBackup="true"

13
android/build.gradle

@ -5,7 +5,7 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.3.2' classpath 'com.android.tools.build:gradle:3.4.2'
} }
} }
@ -16,18 +16,19 @@ repositories {
maven { maven {
url 'https://maven.google.com' url 'https://maven.google.com'
} }
google()
} }
dependencies { dependencies {
implementation 'com.android.support:support-compat:28.0.0' implementation 'androidx.core:core:1.0.2'
} }
android { android {
compileSdkVersion 28 compileSdkVersion 29
buildToolsVersion "28.0.3" buildToolsVersion "28.0.3"
defaultConfig { defaultConfig {
applicationId "org.purplei2p.i2pd" applicationId "org.purplei2p.i2pd"
targetSdkVersion 28 targetSdkVersion 29
minSdkVersion 14 minSdkVersion 14
versionCode 2270 versionCode 2270
versionName "2.27.0" versionName "2.27.0"
@ -81,4 +82,8 @@ android {
path './jni/Android.mk' path './jni/Android.mk'
} }
} }
compileOptions {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
} }

2
android/gradle.properties

@ -1 +1,3 @@
android.enableJetifier=true
android.useAndroidX=true
org.gradle.parallel=true org.gradle.parallel=true

4
android/gradle/wrapper/gradle-wrapper.properties vendored

@ -1,6 +1,6 @@
#Thu Mar 14 18:21:08 MSK 2019 #Tue Aug 20 14:39:08 MSK 2019
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip

5
android/res/values/strings.xml

@ -17,4 +17,9 @@
<string name="remaining">remaining</string> <string name="remaining">remaining</string>
<string name="title_activity_i2_pdperms_asker_prompt">Prompt</string> <string name="title_activity_i2_pdperms_asker_prompt">Prompt</string>
<string name="permDenied">SD card write permission denied, you need to allow this to continue</string> <string name="permDenied">SD card write permission denied, you need to allow this to continue</string>
<string name="battery_optimizations_enabled">Battery optimizations enabled</string>
<string name="battery_optimizations_enabled_explained">Your device is doing some heavy battery optimizations on I2PD that might lead to daemon closing with no other reason.\nIt is recommended to disable those battery optimizations.</string>
<string name="battery_optimizations_enabled_dialog">Your device is doing some heavy battery optimizations on I2PD that might lead to daemon closing with no other reason.\n\nYou will now be asked to disable those.</string>
<string name="next">Next</string>
<string name="device_does_not_support_disabling_battery_optimizations">Your device does not support opting out of battery optimizations</string>
</resources> </resources>

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

@ -1,6 +1,5 @@
package org.purplei2p.i2pd; package org.purplei2p.i2pd;
import android.annotation.TargetApi;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
@ -11,10 +10,9 @@ import android.content.Intent;
import android.os.Binder; import android.os.Binder;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.support.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import android.support.v4.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import android.util.Log; import android.util.Log;
import android.widget.Toast;
public class ForegroundService extends Service { public class ForegroundService extends Service {
private static final String TAG="FgService"; private static final String TAG="FgService";
@ -112,14 +110,15 @@ public class ForegroundService extends Service {
// If earlier version channel ID is not used // 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) // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
String channelId = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) ? createNotificationChannel() : ""; String channelId = Build.VERSION.SDK_INT >= 26 ? createNotificationChannel() : "";
// Set the info for the views that show in the notification panel. // Set the info for the views that show in the notification panel.
Notification notification = new NotificationCompat.Builder(this, channelId) NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId)
.setOngoing(true) .setOngoing(true)
.setSmallIcon(R.drawable.itoopie_notification_icon) // the status icon .setSmallIcon(R.drawable.itoopie_notification_icon); // the status icon
.setPriority(Notification.PRIORITY_DEFAULT) if(Build.VERSION.SDK_INT >= 16) builder = builder.setPriority(Notification.PRIORITY_DEFAULT);
.setCategory(Notification.CATEGORY_SERVICE) if(Build.VERSION.SDK_INT >= 21) builder = builder.setCategory(Notification.CATEGORY_SERVICE);
Notification notification = builder
.setTicker(text) // the status text .setTicker(text) // the status text
.setWhen(System.currentTimeMillis()) // the time stamp .setWhen(System.currentTimeMillis()) // the time stamp
.setContentTitle(getText(R.string.app_name)) // the label of the entry .setContentTitle(getText(R.string.app_name)) // the label of the entry
@ -141,7 +140,8 @@ public class ForegroundService extends Service {
//chan.setLightColor(Color.PURPLE); //chan.setLightColor(Color.PURPLE);
chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE); chan.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
NotificationManager service = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE); NotificationManager service = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
service.createNotificationChannel(chan); if(service!=null)service.createNotificationChannel(chan);
else Log.e(TAG, "error: NOTIFICATION_SERVICE is null");
return channelId; return channelId;
} }

208
android/src/org/purplei2p/i2pd/I2PDActivity.java

@ -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,10 +64,7 @@ public class I2PDActivity extends Activity {
public void daemonStateUpdate() public void daemonStateUpdate()
{ {
processAssets(); processAssets();
runOnUiThread(new Runnable(){ runOnUiThread(() -> {
@Override
public void run() {
try { try {
if(textView==null) return; if(textView==null) return;
Throwable tr = daemon.getLastThrowable(); Throwable tr = daemon.getLastThrowable();
@ -65,15 +73,12 @@ public class I2PDActivity extends Activity {
return; return;
} }
DaemonSingleton.State state = daemon.getState(); DaemonSingleton.State state = daemon.getState();
textView.setText( String startResultStr = DaemonSingleton.State.startFailed.equals(state) ? String.format(": %s", daemon.getDaemonStartResult()) : "";
String.valueOf(getText(state.getStatusStringResourceId()))+ String graceStr = DaemonSingleton.State.gracefulShutdownInProgress.equals(state) ? String.format(": %s %s", formatGraceTimeRemaining(), getText(R.string.remaining)) : "";
(DaemonSingleton.State.startFailed.equals(state) ? ": "+daemon.getDaemonStartResult() : "")+ textView.setText(String.format("%s%s%s", getText(state.getStatusStringResourceId()), startResultStr, graceStr));
(DaemonSingleton.State.gracefulShutdownInProgress.equals(state) ? ": "+formatGraceTimeRemaining()+" "+getText(R.string.remaining) : "")
);
} catch (Throwable tr) { } catch (Throwable tr) {
Log.e(TAG,"error ignored",tr); Log.e(TAG,"error ignored",tr);
} }
}
}); });
} }
}; };
@ -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,20 +145,16 @@ 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)
{
case MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE:
{ {
if (requestCode == MY_PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
Log.e(TAG, "Memory permission granted"); Log.e(TAG, "WR_EXT_STORAGE perm granted");
else else {
Log.e(TAG, "Memory permission declined"); Log.e(TAG, "WR_EXT_STORAGE perm declined, stopping i2pd");
// TODO: terminate i2pdStop();
return; //TODO must work w/o this perm, ask orignal
} }
default: ;
} }
} }
@ -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,18 +262,14 @@ public class I2PDActivity extends Activity {
private void i2pdStop() { private void i2pdStop() {
cancelGracefulStop(); cancelGracefulStop();
new Thread(new Runnable(){ new Thread(() -> {
@Override
public void run() {
Log.d(TAG, "stopping"); Log.d(TAG, "stopping");
try { try {
daemon.stopDaemon(); daemon.stopDaemon();
} catch (Throwable tr) { } catch (Throwable tr) {
Log.e(TAG, "", 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(); },"stop").start();
} }
@ -288,10 +288,7 @@ 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(() -> {
@Override
public void run() {
try { try {
Log.d(TAG, "grac stopping"); Log.d(TAG, "grac stopping");
if(daemon.isStartedOkay()) { if(daemon.isStartedOkay()) {
@ -308,8 +305,6 @@ public class I2PDActivity extends Activity {
} catch(Throwable tr) { } catch(Throwable tr) {
Log.e(TAG,"",tr); Log.e(TAG,"",tr);
} }
}
},"gracInit").start(); },"gracInit").start();
} }
@ -317,11 +312,7 @@ public class I2PDActivity extends Activity {
{ {
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(() -> {
{
@Override
public void run()
{
try try
{ {
Log.d(TAG, "grac stopping cancel"); Log.d(TAG, "grac stopping cancel");
@ -334,8 +325,6 @@ public class I2PDActivity extends Activity {
{ {
Log.e(TAG,"",tr); Log.e(TAG,"",tr);
} }
}
},"gracCancel").start(); },"gracCancel").start();
} }
@ -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();
if(files!=null) {
for (File child : files) {
deleteRecursive(child); 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 holderfile = new File(i2pdpath, "assets.ready"); File holderFile = 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
FileReader fileReader = new FileReader(holderFile);
try {
BufferedReader br = new BufferedReader(fileReader);
try {
String line; String line;
while ((line = br.readLine()) != null) { while ((line = br.readLine()) != null) {
text.append(line); text.append(line);
} }
}finally {
try{
br.close(); br.close();
} catch (IOException e) {
Log.e(TAG, "", e);
}
} }
catch (IOException e) { } finally {
try{
fileReader.close();
} catch (IOException e) {
Log.e(TAG, "", 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);
try {
writer.append(versionName); writer.append(versionName);
writer.flush(); } finally {
try{
writer.close(); 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);
} }
} }

Loading…
Cancel
Save