diff --git a/app/build.gradle b/app/build.gradle index 896a828..a556574 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,11 +22,11 @@ android { disable 'DuplicatePlatformClasses' } - compileSdkVersion 33 + compileSdkVersion 34 defaultConfig { applicationId "org.purplei2p.i2pd" - targetSdkVersion 33 + targetSdkVersion 34 // TODO: 24? minSdkVersion 16 versionCode 2530000 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0eab729..bf16375 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,7 +2,7 @@ - + updateNotificationText(); private void updateNotificationText() { + Log.d(TAG, "FgSvc.updateNotificationText() enter"); try { synchronized (initDeinitLock) { - if (shown) cancelNotification(); - showNotification(); + if (shown){ + Log.d(TAG, "FgSvc.updateNotificationText() calling cancelNotification()"); + cancelNotification(); + } + if(App.isStartDaemon()){ + Log.d(TAG, "FgSvc.updateNotificationText() calling showNotification()"); + showNotification(); + } } } catch (Throwable tr) { - Log.e(TAG,"error ignored",tr); + Log.e(TAG,"error ignored", tr); } + Log.d(TAG, "FgSvc.updateNotificationText() leave"); } @@ -53,7 +58,7 @@ private void updateNotificationText() { // Unique Identification Number for the Notification. // We use it on Notification start, and to cancel it. - private static final int NOTIFICATION = 1; + private static final int NOTIFICATION_ID = 1; /** * Class for clients to access. Because we know this service always @@ -61,9 +66,6 @@ private void updateNotificationText() { * IPC. */ public class LocalBinder extends Binder { - ForegroundService getService() { - return ForegroundService.this; - } } public static void init(DaemonWrapper daemon) { @@ -79,31 +81,51 @@ private static void initCheck() { @Override public void onCreate() { + Log.d(TAG, "FgSvc.onCreate()"); notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); + final App app = (App) getApplication(); + if (daemon == null) daemon = App.getDaemonWrapper(); + if (daemon == null) { + if(App.isStartDaemon()) { + Log.d(TAG, "FgSvc.onCreate() calling app.createDaemonWrapper()"); + app.createDaemonWrapper(); + } + daemon = App.getDaemonWrapper(); + } instance = this; initCheck(); + Log.d(TAG, "FgSvc.onCreate() leave"); } private void setListener() { - daemon.addStateChangeListener(daemonStateUpdatedListener); + final DaemonWrapper dw = daemon; + if (dw != null) dw.addStateChangeListener(daemonStateUpdatedListener); updateNotificationText(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { - Log.d("ForegroundService", "Received start id " + startId + ": " + intent); + Log.d(TAG, "Received start id " + startId + ": " + intent); + readFlags(flags); return START_STICKY; } - + private void readFlags(int flags) { + if ((flags&START_FLAG_REDELIVERY) == START_FLAG_REDELIVERY) + Log.d(TAG,"START_FLAG_REDELIVERY"); + if ((flags&START_FLAG_RETRY) == START_FLAG_RETRY) + Log.d(TAG,"START_FLAG_RETRY"); + } @Override public void onDestroy() { stop(); } public void stop() { + Log.e(TAG,"stop() enter", new Throwable("dumpstack")); cancelNotification(); deinitCheck(); instance = null; + Log.d(TAG,"stop() leave"); } public static void deinit() { @@ -118,14 +140,10 @@ private static void deinitCheck() { } private void cancelNotification() { + Log.d(TAG, "FgSvc.cancelNotification()"); synchronized (initDeinitLock) { - // Cancel the persistent notification. - notificationManager.cancel(NOTIFICATION); - + notificationManager.cancel(NOTIFICATION_ID); stopForeground(true); - - // Tell the user we stopped. - //Toast.makeText(this, R.string.i2pd_service_stopped, Toast.LENGTH_SHORT).show(); shown = false; } } @@ -143,25 +161,27 @@ public IBinder onBind(Intent intent) { * Show a notification while this service is running. */ private void showNotification() { + Log.d(TAG, "FgSvc.showNotification() enter"); synchronized (initDeinitLock) { + Log.d(TAG, "FgSvc.showNotification(): daemon="+daemon); if (daemon != null) { - // In this sample, we'll use the same text for the ticker and the expanded notification CharSequence text = getText(daemon.getState().getStatusStringResourceId()); + Log.d(TAG, "FgSvc.showNotification(): text="+text); // The PendingIntent to launch our activity if the user selects this notification PendingIntent contentIntent = PendingIntent.getActivity(this, 0, - new Intent(this, I2PDActivity.class), + new Intent(this, I2PDPermsAskerActivity.class), Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? FLAG_IMMUTABLE : 0); - // If earlier version channel ID is not used + // on old Android, the 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 >= Build.VERSION_CODES.O ? 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.ic_notification_icon); // the status icon - builder = builder.setPriority(Notification.PRIORITY_DEFAULT); + .setSmallIcon(R.drawable.ic_notification_icon) // the status icon + .setPriority(Notification.PRIORITY_DEFAULT); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) builder = builder.setCategory(Notification.CATEGORY_SERVICE); Notification notification = builder @@ -174,22 +194,24 @@ private void showNotification() { // Send the notification. //mNM.notify(NOTIFICATION, notification); - startForeground(NOTIFICATION, notification); + Log.d(TAG, "FgSvc.showNotification(): calling startForeground()"); + startForeground(NOTIFICATION_ID, notification); shown = true; } } + Log.d(TAG, "FgSvc.showNotification() leave"); } @RequiresApi(Build.VERSION_CODES.O) private synchronized String createNotificationChannel() { String channelId = getString(R.string.app_name); - CharSequence channelName = "I2Pd service"; + CharSequence channelName = getString(R.string.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"); + else Log.e(TAG, "error: NOTIFICATION_SERVICE is null, haven't called createNotificationChannel"); return channelId; } } diff --git a/app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java b/app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java index 19c3f98..5b26ba4 100644 --- a/app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java +++ b/app/src/main/java/org/purplei2p/i2pd/I2PDActivity.java @@ -117,8 +117,7 @@ public void onCreate(Bundle savedInstanceState) { Log.d(TAG, "onCreate"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - startService(new Intent(this, ForegroundService.class)); - textView = (TextView) findViewById(R.id.appStatusText); + textView = findViewById(R.id.appStatusText); /* HTTPProxyState = (CheckBox) findViewById(R.id.service_httpproxy_box); SOCKSProxyState = (CheckBox) findViewById(R.id.service_socksproxy_box); @@ -126,14 +125,10 @@ public void onCreate(Bundle savedInstanceState) { SAMState = (CheckBox) findViewById(R.id.service_sam_box); I2CPState = (CheckBox) findViewById(R.id.service_i2cp_box);*/ - /*if (getDaemon() == null) { - ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - getDaemon() = new getDaemon()Wrapper(getAssets(), connectivityManager); + App.setStartDaemon(true); + if (getDaemon() == null) { + ((App)getApplication()).createDaemonWrapper(); } - ForegroundService.init(getDaemon()); - - */ - //getDaemon()StateUpdatedListener.getDaemon()StateUpdate(getDaemon()Wrapper.State.uninitialized, App.getgetDaemon()Wrapper().getState()); // request permissions if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { @@ -154,7 +149,6 @@ public void onCreate(Bundle savedInstanceState) { getDaemon().startDaemonIfStopped(getApplicationContext()); getDaemon().addStateChangeListener(daemonStateUpdatedListener); updateStatusText(); - doBindService(); final Timer gracefulQuitTimer = getGracefulQuitTimer(); if (gracefulQuitTimer != null) { @@ -172,19 +166,8 @@ public void onCreate(Bundle savedInstanceState) { protected void onDestroy() { super.onDestroy(); textView = null; - ForegroundService.deinit(); getDaemon().removeStateChangeListener(daemonStateUpdatedListener); //cancelGracefulStop0(); - try { - doUnbindService(); - } catch (IllegalArgumentException ex) { - Log.e(TAG, "throwable caught and ignored", ex); - if (ex.getMessage().startsWith("Service not registered: " + org.purplei2p.i2pd.I2PDActivity.class.getName())) { - Log.i(TAG, "Service not registered exception seems to be normal, not a bug it seems."); - } - } catch (Throwable tr) { - Log.e(TAG, "throwable caught and ignored", tr); - } } @Override @@ -216,58 +199,6 @@ private CharSequence throwableToString(Throwable tr) { 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. diff --git a/app/src/main/java/org/purplei2p/i2pd/I2PDPermsAskerActivity.java b/app/src/main/java/org/purplei2p/i2pd/I2PDPermsAskerActivity.java index 2df2780..43b5cb1 100644 --- a/app/src/main/java/org/purplei2p/i2pd/I2PDPermsAskerActivity.java +++ b/app/src/main/java/org/purplei2p/i2pd/I2PDPermsAskerActivity.java @@ -2,6 +2,7 @@ import android.Manifest; import android.app.Activity; +import android.app.Application; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; @@ -183,19 +184,19 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { } } } else if (requestCode == APP_STORAGE_ACCESS_REQUEST_CODE && resultCode == RESULT_OK) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - if (Environment.isExternalStorageManager()) { - startMainActivity(); - } else { - textview_retry.setText(R.string.permDenied); - textview_retry.setVisibility(TextView.VISIBLE); - button_request_write_ext_storage_perms.setVisibility(Button.VISIBLE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + if (Environment.isExternalStorageManager()) { + startMainActivity(); + } else { + textview_retry.setText(R.string.permDenied); + textview_retry.setVisibility(TextView.VISIBLE); + button_request_write_ext_storage_perms.setVisibility(Button.VISIBLE); - finish(); + finish(); + } + } else { + finish(); // close the app } - } else { - finish(); // close the app } - } } } diff --git a/app/src/main/java/org/purplei2p/i2pd/I2PdQSTileService.java b/app/src/main/java/org/purplei2p/i2pd/I2PdQSTileService.java deleted file mode 100644 index 3a06165..0000000 --- a/app/src/main/java/org/purplei2p/i2pd/I2PdQSTileService.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.purplei2p.i2pd; - -import android.content.Intent; -import android.service.quicksettings.Tile; -import android.service.quicksettings.TileService; -import android.util.Log; - -import android.annotation.TargetApi; -import android.os.Build; - -@TargetApi(Build.VERSION_CODES.N) -public class I2PdQSTileService extends TileService { - - private static final String TAG = "MyQSTileService"; - @Override - public void onClick() { - super.onClick(); - Log.d(TAG, "Tile clicked."); - - try { - // Add the FLAG_ACTIVITY_NEW_TASK flag - Intent intent = new Intent(this, I2PDActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - - startActivityAndCollapse(intent); - } catch (Exception e) { - Log.e(TAG, "Error starting ForegroundService", e); - } - } - - - - @Override - public void onStartListening() { - super.onStartListening(); - Log.d(TAG, "Tile started listening."); - } - - @Override - public void onStopListening() { - super.onStopListening(); - Log.d(TAG, "Tile stopped listening."); - } - - @Override - public void onTileAdded() { - super.onTileAdded(); - Log.d(TAG, "Tile added."); - } - - @Override - public void onTileRemoved() { - super.onTileRemoved(); - Log.d(TAG, "Tile removed."); - } -} diff --git a/app/src/main/java/org/purplei2p/i2pd/I2pdQuickSettingsTileService.java b/app/src/main/java/org/purplei2p/i2pd/I2pdQuickSettingsTileService.java new file mode 100644 index 0000000..1a0aec1 --- /dev/null +++ b/app/src/main/java/org/purplei2p/i2pd/I2pdQuickSettingsTileService.java @@ -0,0 +1,42 @@ +package org.purplei2p.i2pd; + +import android.annotation.SuppressLint; +import android.app.PendingIntent; +import android.content.Intent; +import android.service.quicksettings.TileService; +import android.util.Log; + +import android.annotation.TargetApi; +import android.os.Build; + +/** + * https://developer.android.com/develop/ui/views/quicksettings-tiles + */ +@TargetApi(Build.VERSION_CODES.N) +public class I2pdQuickSettingsTileService extends TileService { + private static final String TAG = "TileService"; + @SuppressLint("StartActivityAndCollapseDeprecated") + @Override + public void onClick() { + super.onClick(); + Log.d(TAG, "QSTile clicked."); + + try { + Intent intent = new Intent(getApplicationContext(), I2PDPermsAskerActivity.class); + if (Build.VERSION.SDK_INT >= 28) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + + //start the activity & collapse the quick settings pane + if (Build.VERSION.SDK_INT < 34) { //Android 14, UPSIDE_DOWN_CAKE + startActivityAndCollapse(intent); + } else { + PendingIntent pendingIntent = PendingIntent.getActivity( + this,0, intent, PendingIntent.FLAG_IMMUTABLE); + startActivityAndCollapse(pendingIntent); + } + } catch (Throwable e) { + Log.e(TAG, "Error while handling qs_tile click", e); + } + } +} diff --git a/app/src/main/java/org/purplei2p/i2pd/SettingsActivity.java b/app/src/main/java/org/purplei2p/i2pd/SettingsActivity.java index 27fe126..1dcfd24 100644 --- a/app/src/main/java/org/purplei2p/i2pd/SettingsActivity.java +++ b/app/src/main/java/org/purplei2p/i2pd/SettingsActivity.java @@ -1,8 +1,11 @@ package org.purplei2p.i2pd; +import android.app.ActionBar; import android.app.Activity; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; @@ -17,6 +20,8 @@ import android.widget.Switch; +import org.purplei2p.i2pd.receivers.BootUpReceiver; + import java.io.File; import java.util.List; import java.util.Objects; @@ -25,18 +30,14 @@ //import org.purplei2p.i2pd.iniedotr.IniEditor; public class SettingsActivity extends Activity { - private String TAG = "i2pdSrvcSettings"; - private File cacheDir; - public static String onBootFileName = "/onBoot"; // just file, empty, if exist the do autostart, if not then no. - - //https://gist.github.com/chandruark/3165a5ee3452f2b9ec7736cf1b4c5ea6 - private void addAutoStartupSwitch() { + private final static String TAG = "Settings"; + /** https://gist.github.com/chandruark/3165a5ee3452f2b9ec7736cf1b4c5ea6 */ + private void maybeStartManufacturerSpecificBootupPermissionManagerActivity() { try { Intent intent = new Intent(); - String manufacturer = android.os.Build.MANUFACTURER .toLowerCase(); - - switch (manufacturer){ + String manufacturer = android.os.Build.MANUFACTURER.toLowerCase(); + switch (manufacturer) { case "xiaomi": intent.setComponent(new ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity")); break; @@ -53,73 +54,45 @@ private void addAutoStartupSwitch() { intent.setComponent(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity")); break; case "oneplus": - intent.setComponent(new ComponentName("com.oneplus.security", "com.oneplus.security.chainlaunch.view.ChainLaunchAppListAct‌​ivity")); + intent.setComponent(new ComponentName("com.oneplus.security", "com.oneplus.security.chainlaunch.view.ChainLaunchAppListActivity")); break; } List list = getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); - if (list.size() > 0) { + if (!list.isEmpty()) { startActivity(intent); } - } catch (Exception e) { - Log.e("exceptionAutostarti2pd" , String.valueOf(e)); - } - - } - - //@Override - private void requestPermission() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (!Settings.canDrawOverlays(this)) { - Intent intent = new Intent( - Settings.ACTION_MANAGE_OVERLAY_PERMISSION, - Uri.parse("package:" + getPackageName()) - ); - startActivityForResult(intent, 232); - } + } catch (Throwable e) { + Log.e(TAG,"", e); } } public void onCreate(Bundle savedInstanceState) { - Log.d(TAG, "onCreate"); + Log.d(TAG,"onCreate"); super.onCreate(savedInstanceState); setContentView(R.layout.activity_settings); - Objects.requireNonNull(getActionBar()).setDisplayHomeAsUpEnabled(true); - Switch autostart_switch = findViewById(R.id.autostart_enable); - Button openPreferences = findViewById(R.id.OpenPreferences); - cacheDir = getApplicationContext().getCacheDir(); - File onBoot = new File(cacheDir.getAbsolutePath() + onBootFileName); - openPreferences.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - Intent intent = new Intent(SettingsActivity.this, MainPreferenceActivity.class); - startActivity(intent); - } + final ActionBar actionBar = getActionBar(); + if(actionBar!=null)actionBar.setDisplayHomeAsUpEnabled(true); + Switch autostartSwitch = findViewById(R.id.autostart_enable); + Button openPreferencesButton = findViewById(R.id.OpenPreferences); + File prefsDir = getApplicationContext().getFilesDir(); + openPreferencesButton.setOnClickListener(view -> { + Intent intent = new Intent(SettingsActivity.this, MainPreferenceActivity.class); + startActivity(intent); }); - autostart_switch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - // do something, the isChecked will be - // true if the switch is in the On position - if (isChecked) { - if (!onBoot.exists()) { - requestPermission(); - addAutoStartupSwitch(); - - try { - if (!onBoot.createNewFile()) - Log.d(TAG, "Cant create new wile on: "+onBoot.getAbsolutePath()); - } catch (Exception e) { - Log.e(TAG, "error: " + e.toString()); - } - } - } else { - if (onBoot.exists()) - onBoot.delete(); - } + final SharedPreferences sharedPrefBootUp = getApplicationContext().getSharedPreferences( + BootUpReceiver.SHARED_PREF_FILE_KEY, Context.MODE_PRIVATE); + autostartSwitch.setOnCheckedChangeListener((view, isChecked) -> { + boolean autostartOnBootPrevValue = sharedPrefBootUp.getBoolean(BootUpReceiver.AUTOSTART_ON_BOOT, true); + SharedPreferences.Editor editor = sharedPrefBootUp.edit(); + editor.putBoolean(BootUpReceiver.AUTOSTART_ON_BOOT, isChecked); + editor.apply(); + if (isChecked && !autostartOnBootPrevValue) { + maybeStartManufacturerSpecificBootupPermissionManagerActivity(); } }); - if(onBoot.exists()) - autostart_switch.setChecked(true); + boolean autostartOnBoot = sharedPrefBootUp.getBoolean(BootUpReceiver.AUTOSTART_ON_BOOT, true); + autostartSwitch.setChecked(autostartOnBoot); } public boolean onOptionsItemSelected(MenuItem item) { diff --git a/app/src/main/java/org/purplei2p/i2pd/WebConsoleActivity.java b/app/src/main/java/org/purplei2p/i2pd/WebConsoleActivity.java index 7df7c2a..563d958 100644 --- a/app/src/main/java/org/purplei2p/i2pd/WebConsoleActivity.java +++ b/app/src/main/java/org/purplei2p/i2pd/WebConsoleActivity.java @@ -3,45 +3,62 @@ import android.app.Activity; import android.os.Bundle; import android.os.Handler; +import android.util.Log; import android.view.MenuItem; +import android.webkit.WebResourceError; +import android.webkit.WebResourceRequest; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import java.util.Objects; +import java.util.Timer; public class WebConsoleActivity extends Activity { + private static final String TAG="WebConsole"; private WebView webView; private SwipeRefreshLayout swipeRefreshLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + final String url = "http://localhost:7070/"; setContentView(R.layout.activity_web_console); Objects.requireNonNull(getActionBar()).setDisplayHomeAsUpEnabled(true); - webView = (WebView) findViewById(R.id.webconsole); - webView.setWebViewClient(new WebViewClient()); + webView = findViewById(R.id.webconsole); + WebViewClient webViewClient = new WebViewClient() { + @Override + public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { + super.onReceivedError(view, errorCode, description, failingUrl); + if(url.equals(failingUrl)) + new Thread(() -> { + try { + Thread.sleep(100); + new Handler().post(() -> { + webView.reload(); + }); + }catch(Throwable e){ + Log.e(TAG, "", e); + } + }, "timer-web-err-thread").start(); + } + }; + webView.setWebViewClient(webViewClient); final WebSettings webSettings = webView.getSettings(); webSettings.setBuiltInZoomControls(true); webSettings.setJavaScriptEnabled(false); - webView.loadUrl("http://localhost:7070/"/*I2PD_JNI.getWebConsAddr()*/); + webView.loadUrl(url/*I2PD_JNI.getWebConsAddr()*/); - swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe); - swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { - @Override - public void onRefresh() { - swipeRefreshLayout.setRefreshing(true); - new Handler().post(new Runnable() { - @Override - public void run() { - swipeRefreshLayout.setRefreshing(false); - webView.reload(); - } - }); - } + swipeRefreshLayout = findViewById(R.id.swipe); + swipeRefreshLayout.setOnRefreshListener(() -> { + swipeRefreshLayout.setRefreshing(true); + new Handler().post(() -> { + swipeRefreshLayout.setRefreshing(false); + webView.reload(); + }); }); } diff --git a/app/src/main/java/org/purplei2p/i2pd/appscope/App.java b/app/src/main/java/org/purplei2p/i2pd/appscope/App.java index d624b96..d4aa590 100644 --- a/app/src/main/java/org/purplei2p/i2pd/appscope/App.java +++ b/app/src/main/java/org/purplei2p/i2pd/appscope/App.java @@ -1,22 +1,42 @@ package org.purplei2p.i2pd.appscope; +import android.Manifest; import android.app.Application; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.net.ConnectivityManager; +import android.os.Build; +import android.os.Environment; import android.os.IBinder; import android.util.Log; +import androidx.core.content.ContextCompat; + import org.purplei2p.i2pd.BuildConfig; import org.purplei2p.i2pd.I2PDActivity; import org.purplei2p.i2pd.*; +import org.purplei2p.i2pd.receivers.BootUpReceiver; + +import java.lang.reflect.Method; public class App extends Application { - private static final String TAG = "i2pd.app"; + private static final String TAG = "App"; + + private static volatile boolean startDaemon = false; + + public static synchronized boolean isStartDaemon() { + return startDaemon; + } + + public static synchronized void setStartDaemon(boolean startDaemon) { + App.startDaemon = startDaemon; + } - //private static final I2PD_JNI jniHolder = new I2PD_JNI(); +//private static final I2PD_JNI jniHolder = new I2PD_JNI(); private static volatile DaemonWrapper daemonWrapper; private String versionName; @@ -24,28 +44,62 @@ public class App extends Application { private static volatile boolean mIsBound; - public synchronized static DaemonWrapper getDaemonWrapper() { return daemonWrapper; } + public static void startForegroundService(Context context) { + Intent serviceIntent = new Intent(context, ForegroundService.class); + serviceIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ContextCompat.startForegroundService(context, serviceIntent); + } else { + context.startService(serviceIntent); + } + } + + public static void maybeAutostartForegroundServiceOnBoot(Context context) { + boolean autostartOnBoot = isAutostartOnBoot(context); + if(autostartOnBoot) { + startForegroundService(context); + } + } + + public static boolean isAutostartOnBoot(Context context) { + SharedPreferences sharedPref = context.getSharedPreferences( + BootUpReceiver.SHARED_PREF_FILE_KEY, MODE_PRIVATE); + boolean autostartOnBoot = sharedPref.getBoolean(BootUpReceiver.AUTOSTART_ON_BOOT, true); + return autostartOnBoot; + } + @Override public void onCreate() { + Log.d(TAG, "App.onCreate"); super.onCreate(); - synchronized (this) { - if (getDaemonWrapper() == null) { - createDaemonWrapper(); - } - versionName = BuildConfig.VERSION_NAME; + if(App.isAutostartOnBoot(getApplicationContext())){ + Log.d(TAG, "calling App.setStartDaemon(true)"); + App.setStartDaemon(true); + } + if(App.isStartDaemon()){ + Log.d(TAG, "calling App.doBindService()"); doBindService(); - startService(new Intent(this, ForegroundService.class)); } + Log.d(TAG, "calling App.maybeAutostartForegroundServiceOnBoot()"); + maybeAutostartForegroundServiceOnBoot(getApplicationContext()); +// synchronized (this) { +// if (getDaemonWrapper() == null) { +// createDaemonWrapper(); +// } + versionName = BuildConfig.VERSION_NAME; +// startService(new Intent(this, ForegroundService.class)); +// } + Log.d(TAG, "App.onCreate() leave"); } - private void createDaemonWrapper() { - ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService( + public void createDaemonWrapper() { + ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService( Context.CONNECTIVITY_SERVICE); - daemonWrapper = new DaemonWrapper(getApplicationContext(), getAssets(), connectivityManager); + daemonWrapper = new DaemonWrapper(this, getApplicationContext(), getAssets(), connectivityManager); ForegroundService.init(daemonWrapper); } @@ -101,10 +155,11 @@ public void onServiceDisconnected(ComponentName className) { public synchronized void quit() { try { - if(daemonWrapper!=null)daemonWrapper.stopDaemon(null); + if (daemonWrapper != null) daemonWrapper.stopDaemon(null); } catch (Throwable tr) { Log.e(TAG, "", tr); } + try { doUnbindService(); } catch (IllegalArgumentException ex) { @@ -115,12 +170,38 @@ public synchronized void quit() { } catch (Throwable tr) { Log.e(TAG, "throwable caught and ignored", tr); } - try{ + try { + Log.d(TAG, "calling fgservice.stop"); ForegroundService fs = ForegroundService.getInstance(); - if(fs!=null)fs.stop(); - }catch(Throwable tr) { + if (fs != null) fs.stop(); + } catch (Throwable tr) { Log.e(TAG, "", tr); } + } + + public boolean isPermittedToWriteToExternalStorage() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + return Environment.isExternalStorageManager(); + } else { + Method methodCheckPermission; + + try { + methodCheckPermission = getClass().getMethod( + "checkSelfPermission", String.class); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + + Integer resultObj; + try { + resultObj = (Integer) methodCheckPermission.invoke( + this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE); + } catch (Throwable e) { + throw new RuntimeException(e); + } + + return resultObj == PackageManager.PERMISSION_GRANTED; + } } } diff --git a/app/src/main/java/org/purplei2p/i2pd/receivers/BootUpReceiver.java b/app/src/main/java/org/purplei2p/i2pd/receivers/BootUpReceiver.java index 2a4078f..8ba5862 100644 --- a/app/src/main/java/org/purplei2p/i2pd/receivers/BootUpReceiver.java +++ b/app/src/main/java/org/purplei2p/i2pd/receivers/BootUpReceiver.java @@ -4,28 +4,17 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -//import org.purplei2p.i2pd.ForegroundService; -//ToDo:* fix^^^ change to service, not on window on start. -import org.purplei2p.i2pd.I2PDPermsAskerActivity; +import org.purplei2p.i2pd.appscope.App; -import java.io.File; +public class BootUpReceiver extends BroadcastReceiver { -import static org.purplei2p.i2pd.SettingsActivity.onBootFileName; + public static final String AUTOSTART_ON_BOOT = "autostart_on_boot"; + public static final String SHARED_PREF_FILE_KEY = BootUpReceiver.class.getName(); -public class BootUpReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - /* todo: disable the autostart? */ - File onBoot = - new File( - context.getApplicationContext().getCacheDir().getAbsolutePath() - + onBootFileName); - if(onBoot.exists()) { - Intent i = new Intent(context, I2PDPermsAskerActivity.class); - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(i); - } - + if(App.isAutostartOnBoot(context))App.setStartDaemon(true); + App.maybeAutostartForegroundServiceOnBoot(context); } } diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b867971..894a233 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -16,7 +16,7 @@ Приложение инициализируется... Приложение запускается... Загружены JNI библиотеки - Приложение запущено + Приложение работает Запуск не удался Приложение было остановлено Остановка приложения... diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index cd965f4..37985aa 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -61,5 +61,6 @@ Delete tunnel Open i2pd settings + I2Pd service