aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpacien2020-12-08 18:03:20 +0100
committerpacien2020-12-08 18:03:20 +0100
commit355251694d63640f028f3e2c17235d12a8573df6 (patch)
tree966ffd45ec29797afb440ead00586f4fcdf15589
parent2760703484f9b12f8c21c395915f9780b1ae7e9e (diff)
downloadtincapp-355251694d63640f028f3e2c17235d12a8573df6.tar.gz
ConfigurationAccessService: prevent service from being stopped when app loses focus
This makes the ConfigurationAccessService (formerly ConfigurationFtpService) start in foreground through the use of a persistent notification so that it isn't stopped by the system after the app loses the focus on the user's screen, which happens when the user switches to an FTP client application on the same device.
-rw-r--r--app/src/main/AndroidManifest.xml5
-rw-r--r--app/src/main/java/org/pacien/tincapp/activities/configure/ConfigurationAccessServerFragment.kt (renamed from app/src/main/java/org/pacien/tincapp/activities/configure/ConfigurationFtpServerFragment.kt)24
-rw-r--r--app/src/main/java/org/pacien/tincapp/context/AppNotificationManager.kt39
-rw-r--r--app/src/main/java/org/pacien/tincapp/service/ConfigurationAccessService.kt (renamed from app/src/main/java/org/pacien/tincapp/service/ConfigurationFtpService.kt)26
-rw-r--r--app/src/main/res/drawable/ic_baseline_folder_open_primary_24dp.xml29
-rw-r--r--app/src/main/res/layout/configure_activity.xml4
-rw-r--r--app/src/main/res/layout/configure_tools_configuration_access_fragment.xml (renamed from app/src/main/res/layout/configure_tools_configuration_ftp_server_fragment.xml)0
-rw-r--r--app/src/main/res/values/strings.xml6
8 files changed, 105 insertions, 28 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 020d62d..c98555f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -24,6 +24,9 @@
24 24
25 <uses-permission android:name="android.permission.INTERNET" /> 25 <uses-permission android:name="android.permission.INTERNET" />
26 26
27 <!-- needed for the configuration FTP server -->
28 <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
29
27 <!-- workaround for broken file permissions on some Android ROMs --> 30 <!-- workaround for broken file permissions on some Android ROMs -->
28 <uses-permission-sdk-23 android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> 31 <uses-permission-sdk-23 android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
29 32
@@ -82,7 +85,7 @@
82 </service> 85 </service>
83 86
84 <service 87 <service
85 android:name="org.pacien.tincapp.service.ConfigurationFtpService"> 88 android:name="org.pacien.tincapp.service.ConfigurationAccessService">
86 </service> 89 </service>
87 90
88 </application> 91 </application>
diff --git a/app/src/main/java/org/pacien/tincapp/activities/configure/ConfigurationFtpServerFragment.kt b/app/src/main/java/org/pacien/tincapp/activities/configure/ConfigurationAccessServerFragment.kt
index b97a15e..9fee749 100644
--- a/app/src/main/java/org/pacien/tincapp/activities/configure/ConfigurationFtpServerFragment.kt
+++ b/app/src/main/java/org/pacien/tincapp/activities/configure/ConfigurationAccessServerFragment.kt
@@ -26,43 +26,43 @@ import android.view.ViewGroup
26import androidx.databinding.Observable 26import androidx.databinding.Observable
27import androidx.databinding.ObservableBoolean 27import androidx.databinding.ObservableBoolean
28import org.pacien.tincapp.activities.BaseFragment 28import org.pacien.tincapp.activities.BaseFragment
29import org.pacien.tincapp.databinding.ConfigureToolsConfigurationFtpServerFragmentBinding 29import org.pacien.tincapp.databinding.ConfigureToolsConfigurationAccessFragmentBinding
30import org.pacien.tincapp.service.ConfigurationFtpService 30import org.pacien.tincapp.service.ConfigurationAccessService
31 31
32/** 32/**
33 * @author pacien 33 * @author pacien
34 */ 34 */
35class ConfigurationFtpServerFragment : BaseFragment() { 35class ConfigurationAccessServerFragment : BaseFragment() {
36 private val ftpServerStartListener = object : Observable.OnPropertyChangedCallback() { 36 private val ftpServerStartListener = object : Observable.OnPropertyChangedCallback() {
37 override fun onPropertyChanged(sender: Observable, propertyId: Int) { 37 override fun onPropertyChanged(sender: Observable, propertyId: Int) {
38 binding.ftpEnabled = (sender as ObservableBoolean).get() 38 binding.ftpEnabled = (sender as ObservableBoolean).get()
39 } 39 }
40 } 40 }
41 41
42 private lateinit var binding: ConfigureToolsConfigurationFtpServerFragmentBinding 42 private lateinit var binding: ConfigureToolsConfigurationAccessFragmentBinding
43 43
44 override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { 44 override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
45 binding = ConfigureToolsConfigurationFtpServerFragmentBinding.inflate(inflater, container, false) 45 binding = ConfigureToolsConfigurationAccessFragmentBinding.inflate(inflater, container, false)
46 binding.ftpUsername = ConfigurationFtpService.FTP_USERNAME 46 binding.ftpUsername = ConfigurationAccessService.FTP_USERNAME
47 binding.ftpPassword = ConfigurationFtpService.FTP_PASSWORD 47 binding.ftpPassword = ConfigurationAccessService.FTP_PASSWORD
48 binding.ftpPort = ConfigurationFtpService.FTP_PORT 48 binding.ftpPort = ConfigurationAccessService.FTP_PORT
49 binding.toggleFtpState = { toggleServer() } 49 binding.toggleFtpState = { toggleServer() }
50 return binding.root 50 return binding.root
51 } 51 }
52 52
53 override fun onResume() { 53 override fun onResume() {
54 super.onResume() 54 super.onResume()
55 ConfigurationFtpService.runningState.addOnPropertyChangedCallback(ftpServerStartListener) 55 ConfigurationAccessService.runningState.addOnPropertyChangedCallback(ftpServerStartListener)
56 binding.ftpEnabled = ConfigurationFtpService.runningState.get() 56 binding.ftpEnabled = ConfigurationAccessService.runningState.get()
57 } 57 }
58 58
59 override fun onPause() { 59 override fun onPause() {
60 ConfigurationFtpService.runningState.removeOnPropertyChangedCallback(ftpServerStartListener) 60 ConfigurationAccessService.runningState.removeOnPropertyChangedCallback(ftpServerStartListener)
61 super.onPause() 61 super.onPause()
62 } 62 }
63 63
64 private fun toggleServer() { 64 private fun toggleServer() {
65 val targetServiceIntent = Intent(requireContext(), ConfigurationFtpService::class.java) 65 val targetServiceIntent = Intent(requireContext(), ConfigurationAccessService::class.java)
66 66
67 if (binding.ftpEnabled) 67 if (binding.ftpEnabled)
68 requireContext().stopService(targetServiceIntent) 68 requireContext().stopService(targetServiceIntent)
diff --git a/app/src/main/java/org/pacien/tincapp/context/AppNotificationManager.kt b/app/src/main/java/org/pacien/tincapp/context/AppNotificationManager.kt
index 38bf6e4..9d731a5 100644
--- a/app/src/main/java/org/pacien/tincapp/context/AppNotificationManager.kt
+++ b/app/src/main/java/org/pacien/tincapp/context/AppNotificationManager.kt
@@ -1,6 +1,6 @@
1/* 1/*
2 * Tinc App, an Android binding and user interface for the tinc mesh VPN daemon 2 * Tinc App, an Android binding and user interface for the tinc mesh VPN daemon
3 * Copyright (C) 2017-2019 Pacien TRAN-GIRARD 3 * Copyright (C) 2017-2020 Pacien TRAN-GIRARD
4 * 4 *
5 * This program is free software: you can redistribute it and/or modify 5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by 6 * it under the terms of the GNU General Public License as published by
@@ -18,6 +18,7 @@
18 18
19package org.pacien.tincapp.context 19package org.pacien.tincapp.context
20 20
21import android.app.Notification
21import android.app.NotificationChannel 22import android.app.NotificationChannel
22import android.app.NotificationManager 23import android.app.NotificationManager
23import android.app.PendingIntent 24import android.app.PendingIntent
@@ -35,16 +36,19 @@ import org.pacien.tincapp.R
35 */ 36 */
36class AppNotificationManager(private val context: Context) { 37class AppNotificationManager(private val context: Context) {
37 companion object { 38 companion object {
38 private const val CHANNEL_ID = "org.pacien.tincapp.notification.channels.error" 39 private const val ERROR_CHANNEL_ID = "org.pacien.tincapp.notification.channels.error"
39 private const val ERROR_NOTIFICATION_ID = 0 40 private const val CONFIG_ACCESS_CHANNEL_ID = "org.pacien.tincapp.notification.channels.configuration"
41
42 const val ERROR_NOTIFICATION_ID = 0
43 const val CONFIG_ACCESS_NOTIFICATION_ID = 1
40 } 44 }
41 45
42 init { 46 init {
43 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) registerChannel() 47 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) registerChannels()
44 } 48 }
45 49
46 fun notifyError(title: String, message: String, manualLink: String? = null) { 50 fun notifyError(title: String, message: String, manualLink: String? = null) {
47 val notification = NotificationCompat.Builder(context, CHANNEL_ID) 51 val notification = NotificationCompat.Builder(context, ERROR_CHANNEL_ID)
48 .setSmallIcon(R.drawable.ic_warning_primary_24dp) 52 .setSmallIcon(R.drawable.ic_warning_primary_24dp)
49 .setContentTitle(title) 53 .setContentTitle(title)
50 .setContentText(message) 54 .setContentText(message)
@@ -62,13 +66,26 @@ class AppNotificationManager(private val context: Context) {
62 NotificationManagerCompat.from(context).cancelAll() 66 NotificationManagerCompat.from(context).cancelAll()
63 } 67 }
64 68
69 fun newConfigurationAccessNotificationBuilder() =
70 NotificationCompat.Builder(context, CONFIG_ACCESS_CHANNEL_ID)
71
65 @RequiresApi(Build.VERSION_CODES.O) 72 @RequiresApi(Build.VERSION_CODES.O)
66 private fun registerChannel() { 73 private fun registerChannels() {
67 val name = context.getString(R.string.notification_error_channel_name) 74 context.getSystemService(NotificationManager::class.java)
68 val importance = NotificationManager.IMPORTANCE_HIGH 75 .apply {
69 val channel = NotificationChannel(CHANNEL_ID, name, importance) 76 createNotificationChannel(NotificationChannel(
70 val notificationManager = context.getSystemService(NotificationManager::class.java) 77 ERROR_CHANNEL_ID,
71 notificationManager.createNotificationChannel(channel) 78 context.getString(R.string.notification_error_channel_name),
79 NotificationManager.IMPORTANCE_HIGH
80 ))
81 }
82 .apply {
83 createNotificationChannel(NotificationChannel(
84 CONFIG_ACCESS_CHANNEL_ID,
85 context.getString(R.string.notification_config_access_channel_name),
86 NotificationManager.IMPORTANCE_MIN
87 ))
88 }
72 } 89 }
73 90
74 private fun NotificationCompat.Builder.setHighPriority() = apply { 91 private fun NotificationCompat.Builder.setHighPriority() = apply {
diff --git a/app/src/main/java/org/pacien/tincapp/service/ConfigurationFtpService.kt b/app/src/main/java/org/pacien/tincapp/service/ConfigurationAccessService.kt
index 2ea4a16..b083a83 100644
--- a/app/src/main/java/org/pacien/tincapp/service/ConfigurationFtpService.kt
+++ b/app/src/main/java/org/pacien/tincapp/service/ConfigurationAccessService.kt
@@ -18,6 +18,7 @@
18 18
19package org.pacien.tincapp.service 19package org.pacien.tincapp.service
20 20
21import android.app.PendingIntent
21import android.app.Service 22import android.app.Service
22import android.content.Intent 23import android.content.Intent
23import android.os.IBinder 24import android.os.IBinder
@@ -31,7 +32,9 @@ import org.apache.ftpserver.listener.ListenerFactory
31import org.apache.ftpserver.usermanager.UsernamePasswordAuthentication 32import org.apache.ftpserver.usermanager.UsernamePasswordAuthentication
32import org.apache.ftpserver.usermanager.impl.WritePermission 33import org.apache.ftpserver.usermanager.impl.WritePermission
33import org.pacien.tincapp.R 34import org.pacien.tincapp.R
35import org.pacien.tincapp.activities.configure.ConfigureActivity
34import org.pacien.tincapp.context.App 36import org.pacien.tincapp.context.App
37import org.pacien.tincapp.context.AppNotificationManager
35import org.pacien.tincapp.extensions.Java.defaultMessage 38import org.pacien.tincapp.extensions.Java.defaultMessage
36import org.slf4j.LoggerFactory 39import org.slf4j.LoggerFactory
37import java.io.IOException 40import java.io.IOException
@@ -42,7 +45,7 @@ import java.io.IOException
42 * 45 *
43 * @author pacien 46 * @author pacien
44 */ 47 */
45class ConfigurationFtpService : Service() { 48class ConfigurationAccessService : Service() {
46 companion object { 49 companion object {
47 // Apache Mina FtpServer's INFO log level is actually VERBOSE. 50 // Apache Mina FtpServer's INFO log level is actually VERBOSE.
48 // The object holds static references to those loggers so that they stay around. 51 // The object holds static references to those loggers so that they stay around.
@@ -63,6 +66,7 @@ class ConfigurationFtpService : Service() {
63 } 66 }
64 67
65 private val log by lazy { LoggerFactory.getLogger(this.javaClass)!! } 68 private val log by lazy { LoggerFactory.getLogger(this.javaClass)!! }
69 private val notificationManager by lazy { App.notificationManager }
66 private var sftpServer: FtpServer? = null 70 private var sftpServer: FtpServer? = null
67 71
68 override fun onBind(intent: Intent): IBinder? = null // non-bindable service 72 override fun onBind(intent: Intent): IBinder? = null // non-bindable service
@@ -82,6 +86,7 @@ class ConfigurationFtpService : Service() {
82 it.start() 86 it.start()
83 runningState.set(true) 87 runningState.set(true)
84 log.info("Started FTP server on port {}", FTP_PORT) 88 log.info("Started FTP server on port {}", FTP_PORT)
89 pinInForeground()
85 } catch (e: IOException) { 90 } catch (e: IOException) {
86 log.error("Could not start FTP server", e) 91 log.error("Could not start FTP server", e)
87 App.alert(R.string.notification_error_title_unable_to_start_ftp_server, e.defaultMessage()) 92 App.alert(R.string.notification_error_title_unable_to_start_ftp_server, e.defaultMessage())
@@ -91,6 +96,25 @@ class ConfigurationFtpService : Service() {
91 return START_NOT_STICKY 96 return START_NOT_STICKY
92 } 97 }
93 98
99 /**
100 * Pins the service in the foreground so that it doesn't get stopped by the system when the
101 * application's activities are put in the background, which is the case when the user sets the
102 * focus on an FTP client app for example.
103 */
104 private fun pinInForeground() {
105 startForeground(
106 AppNotificationManager.CONFIG_ACCESS_NOTIFICATION_ID,
107 notificationManager.newConfigurationAccessNotificationBuilder()
108 .setSmallIcon(R.drawable.ic_baseline_folder_open_primary_24dp)
109 .setContentTitle(resources.getString(R.string.notification_config_access_server_running_title))
110 .setContentText(resources.getString(R.string.notification_config_access_server_running_message))
111 .setContentIntent(Intent(this, ConfigureActivity::class.java).let {
112 PendingIntent.getActivity(this, 0, it, 0)
113 })
114 .build()
115 )
116 }
117
94 private fun setupSingleUserServer(ftpUser: User): FtpServer { 118 private fun setupSingleUserServer(ftpUser: User): FtpServer {
95 return FtpServerFactory() 119 return FtpServerFactory()
96 .apply { addListener("default", ListenerFactory().apply { port = FTP_PORT }.createListener()) } 120 .apply { addListener("default", ListenerFactory().apply { port = FTP_PORT }.createListener()) }
diff --git a/app/src/main/res/drawable/ic_baseline_folder_open_primary_24dp.xml b/app/src/main/res/drawable/ic_baseline_folder_open_primary_24dp.xml
new file mode 100644
index 0000000..da24678
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_folder_open_primary_24dp.xml
@@ -0,0 +1,29 @@
1<?xml version="1.0" encoding="utf-8"?>
2
3<!--
4 * Material design icon
5 * Copyright 2017 Google Inc.
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18-->
19
20<vector xmlns:android="http://schemas.android.com/apk/res/android"
21 android:width="24dp"
22 android:height="24dp"
23 android:tint="?attr/colorControlNormal"
24 android:viewportWidth="24"
25 android:viewportHeight="24">
26 <path
27 android:fillColor="@color/textTitle"
28 android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8h16v10z" />
29</vector>
diff --git a/app/src/main/res/layout/configure_activity.xml b/app/src/main/res/layout/configure_activity.xml
index 2fde4a2..08f29af 100644
--- a/app/src/main/res/layout/configure_activity.xml
+++ b/app/src/main/res/layout/configure_activity.xml
@@ -30,11 +30,11 @@
30 30
31 <TextView 31 <TextView
32 style="@style/AppTheme.SectionTitle" 32 style="@style/AppTheme.SectionTitle"
33 android:text="@string/configure_activity_title_configuration_server"/> 33 android:text="@string/configure_activity_title_configuration_access"/>
34 34
35 <fragment 35 <fragment
36 android:id="@+id/configure_activity_configuration_sftp_server_fragment" 36 android:id="@+id/configure_activity_configuration_sftp_server_fragment"
37 android:name="org.pacien.tincapp.activities.configure.ConfigurationFtpServerFragment" 37 android:name="org.pacien.tincapp.activities.configure.ConfigurationAccessServerFragment"
38 android:layout_width="match_parent" 38 android:layout_width="match_parent"
39 android:layout_height="wrap_content"/> 39 android:layout_height="wrap_content"/>
40 40
diff --git a/app/src/main/res/layout/configure_tools_configuration_ftp_server_fragment.xml b/app/src/main/res/layout/configure_tools_configuration_access_fragment.xml
index 24f3c36..24f3c36 100644
--- a/app/src/main/res/layout/configure_tools_configuration_ftp_server_fragment.xml
+++ b/app/src/main/res/layout/configure_tools_configuration_access_fragment.xml
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cae7fe5..6c1a934 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -75,6 +75,10 @@
75 <string name="notification_error_message_network_config_invalid_format">Invalid network configuration in network.conf:\n%1$s</string> 75 <string name="notification_error_message_network_config_invalid_format">Invalid network configuration in network.conf:\n%1$s</string>
76 <string name="notification_error_message_could_not_decrypt_private_keys_format">Could not decrypt private keys:\n%1$s</string> 76 <string name="notification_error_message_could_not_decrypt_private_keys_format">Could not decrypt private keys:\n%1$s</string>
77 77
78 <string name="notification_config_access_channel_name">Configuration access</string>
79 <string name="notification_config_access_server_running_title">Configuration access server active</string>
80 <string name="notification_config_access_server_running_message">The configuration directory is accessible through FTP.</string>
81
78 <!-- Start activity --> 82 <!-- Start activity -->
79 <string name="start_activity_menu_configure">Configure</string> 83 <string name="start_activity_menu_configure">Configure</string>
80 <string name="start_network_list_title">Connect to network</string> 84 <string name="start_network_list_title">Connect to network</string>
@@ -85,7 +89,7 @@
85 <string name="configure_tools_message_invalid_network_name">Invalid network name.</string> 89 <string name="configure_tools_message_invalid_network_name">Invalid network name.</string>
86 <string name="configure_tools_message_network_configuration_written">Network configuration written.</string> 90 <string name="configure_tools_message_network_configuration_written">Network configuration written.</string>
87 91
88 <string name="configure_activity_title_configuration_server">Configuration server</string> 92 <string name="configure_activity_title_configuration_access">Configuration access</string>
89 <string name="configure_configuration_server_ftp_access">FTP access</string> 93 <string name="configure_configuration_server_ftp_access">FTP access</string>
90 <string name="configure_configuration_server_ftp_access_endpoint_format">User: %1$s, password: %2$s, port: %3$d</string> 94 <string name="configure_configuration_server_ftp_access_endpoint_format">User: %1$s, password: %2$s, port: %3$d</string>
91 <string name="configure_configuration_server_ftp_access_not_active">Not active</string> 95 <string name="configure_configuration_server_ftp_access_not_active">Not active</string>