summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2022-08-17 08:49:03 +0000
committerGuillaume Jacquart <guillaume.jacquart@hoodbrains.com>2022-08-17 08:49:03 +0000
commitc8d88ec3364218802bc48257b7766ad8f19a6e45 (patch)
treea767b29c62cb88ec39c3475ee579439a25141474 /app
parent12510a55c9c2b1d21c6e1f45d0058778ddfc9eaa (diff)
2-Simplify sources modules tree
Diffstat (limited to 'app')
-rw-r--r--app/build.gradle98
-rw-r--r--app/src/google/res/values/strings.xml3
-rw-r--r--app/src/main/AndroidManifest.xml32
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt5
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt33
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt23
-rw-r--r--app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt16
-rw-r--r--app/src/standalone/res/values-night/colors.xml28
-rw-r--r--app/src/standalone/res/values/colors.xml27
-rw-r--r--app/src/standalone/res/values/strings.xml3
10 files changed, 190 insertions, 78 deletions
diff --git a/app/build.gradle b/app/build.gradle
index 5f2b302..61ee623 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -16,7 +16,10 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- manifestPlaceholders = [ persistent: "false" ]
+ manifestPlaceholders = [
+ persistent: "false",
+ mainActivityIntentFilterCategory: "android.intent.category.LAUNCHER"
+ ]
resValue("string", "mapbox_key", MAPBOX_KEY)
}
@@ -47,24 +50,36 @@ android {
dimension 'os'
minSdkVersion 29
targetSdkVersion 29
+ signingConfig signingConfigs.eDebug
}
e30 {
dimension 'os'
minSdkVersion 30
targetSdkVersion 30
+ signingConfig signingConfigs.eDebug
+ }
+ standalone {
+ dimension 'os'
+ applicationIdSuffix '.standalone'
+ minSdkVersion 26
+ targetSdkVersion 31
+ manifestPlaceholders = [
+ persistent: "false",
+ mainActivityIntentFilterCategory: "android.intent.category.LAUNCHER"
+ ]
+ signingConfig signingConfigs.debug
}
-// google {
-// applicationIdSuffix '.google'
-// dimension 'os'
-// }
}
buildTypes {
debug {
- signingConfig null // Set signing config to null as we use signingConfig per variant.
+ signingConfig null // Set signing config to null as we use signingConfig per variant.
}
release {
- manifestPlaceholders = [ persistent: "true" ]
+ manifestPlaceholders = [
+ persistent: "true",
+ mainActivityIntentFilterCategory: "android.intent.category.INFO"
+ ]
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
@@ -78,13 +93,6 @@ android {
variant.outputs.all { output ->
outputFileName = "Advanced_Privacy-${variant.versionName}-${variant.getFlavorName()}-${variant.buildType.name}.apk"
}
- if (variant.buildType.name == "debug") {
- if (variant.getFlavorName() == "e29" || variant.getFlavorName() == "e30") {
- variant.mergedFlavor.signingConfig = signingConfigs.eDebug
- } else {
- variant.mergedFlavor.signingConfig = signingConfigs.debug
- }
- }
}
compileOptions {
@@ -103,53 +111,51 @@ android {
}
dependencies {
+ implementation project(':api')
- compileOnly files('libs/e-ui-sdk-1.0.1-q.jar')
- implementation files('libs/lineage-sdk.jar')
- // include the google specific version of the modules, just for the google flavor
- //googleImplementation project(":privacymodulesgoogle")
- // include the e specific version of the modules, just for the e flavor
-
- implementation 'foundation.e:privacymodule.trackerfilter:0.7.0'
- implementation 'foundation.e:privacymodule.api:1.1.0'
- e29Implementation 'foundation.e:privacymodule.e-29:0.4.3'
- e30Implementation 'foundation.e:privacymodule.e-30:0.4.3'
- implementation 'foundation.e:privacymodule.tor:0.2.4'
-
+ standaloneImplementation project(':permissionsstandalone')
+ e29Implementation('foundation.e:privacymodule.e-29:1.2.0') {
+ exclude group: 'foundation.e', module: 'privacymodule.api'
+ }
+ e30Implementation('foundation.e:privacymodule.e-30:1.2.0') {
+ exclude group: 'foundation.e', module: 'privacymodule.api'
+ }
- // implementation Libs.Kotlin.stdlib
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$Versions.kotlin"
-// implementation Libs.AndroidX.coreKtx
- implementation "androidx.core:core-ktx:1.8.0"
-
-// implementation Libs.AndroidX.Fragment.fragmentKtx
- implementation "androidx.fragment:fragment-ktx:$Versions.fragment"
+ implementation project(':fakelocation')
- implementation 'androidx.appcompat:appcompat:1.4.2'
-// implementation Libs.AndroidX.Lifecycle.runtime
- implementation "androidx.lifecycle:lifecycle-runtime-ktx:$Versions.lifecycle"
-// implementation Libs.AndroidX.Lifecycle.viewmodel
- implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$Versions.lifecycle"
+ e29CompileOnly files('libs/e-ui-sdk-1.0.1-q.jar')
+ e29Implementation files('libs/lineage-sdk.jar')
- implementation 'androidx.work:work-runtime-ktx:2.7.1'
+ e30CompileOnly files('libs/e-ui-sdk-1.0.1-q.jar')
+ e30Implementation files('libs/lineage-sdk.jar')
- implementation 'com.google.android.material:material:1.6.1'
+ implementation project(':trackers')
+ implementation 'foundation.e:privacymodule.tor:0.2.4'
- implementation 'com.squareup.retrofit2:retrofit:2.9.0'
- implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
+ implementation (
+ Libs.Kotlin.stdlib,
+ Libs.AndroidX.coreKtx,
+ Libs.AndroidX.appCompat,
+ Libs.AndroidX.Fragment.fragmentKtx,
+ Libs.AndroidX.Lifecycle.runtime,
+ Libs.AndroidX.Lifecycle.viewmodel,
+ Libs.AndroidX.work,
+ Libs.material,
-// implementation Libs.MapBox.sdk
- implementation "com.mapbox.mapboxsdk:mapbox-android-sdk:$Versions.mapbox"
- implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
+ Libs.Retrofit.retrofit,
+ Libs.Retrofit.scalars,
+ Libs.MapBox.sdk,
+ Libs.mpAndroidCharts
+ )
+ debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
- debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1'
}
static def log(Object val) {
diff --git a/app/src/google/res/values/strings.xml b/app/src/google/res/values/strings.xml
deleted file mode 100644
index ebf51d0..0000000
--- a/app/src/google/res/values/strings.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-<resources>
- <string name="app_name">google - PrivacyModulesDemo</string>
-</resources>
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d285b6f..d2a824a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,4 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 ECORP
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="foundation.e.privacycentralapp"
@@ -33,6 +49,7 @@
>
<receiver
android:name=".common.BootCompletedReceiver"
+ android:exported="true"
>
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
@@ -53,19 +70,28 @@
android:resource="@xml/widget_info"
/>
</receiver>
- <receiver android:name=".widget.WidgetCommandReceiver">
+ <receiver android:name=".widget.WidgetCommandReceiver"
+ android:exported="true">
<intent-filter>
<action android:name="toggle_privacy" />
</intent-filter>
</receiver>
<activity android:name=".main.MainActivity"
- android:launchMode="singleTask">
+ android:launchMode="singleTask"
+ android:exported="true"
+ >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.INFO"/>
+ <category android:name="${mainActivityIntentFilterCategory}"/>
</intent-filter>
</activity>
+
+ <service
+ android:name="org.torproject.android.service.OrbotService"
+ android:exported="false"
+
+ />
</application>
</manifest> \ No newline at end of file
diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
index 6be3724..a44a00a 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt
@@ -40,16 +40,15 @@ import foundation.e.privacycentralapp.features.location.FakeLocationViewModel
import foundation.e.privacycentralapp.features.trackers.TrackersViewModel
import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersFragment
import foundation.e.privacycentralapp.features.trackers.apptrackers.AppTrackersViewModel
+import foundation.e.privacymodules.fakelocation.FakeLocationModule
import foundation.e.privacymodules.ipscrambler.IpScramblerModule
import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule
-import foundation.e.privacymodules.location.FakeLocationModule
import foundation.e.privacymodules.location.IFakeLocationModule
import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
import foundation.e.privacymodules.permissions.data.ApplicationDescription
import foundation.e.privacymodules.trackers.api.BlockTrackersPrivacyModule
import foundation.e.privacymodules.trackers.api.TrackTrackersPrivacyModule
import kotlinx.coroutines.DelicateCoroutinesApi
-import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.GlobalScope
/**
@@ -61,7 +60,7 @@ class DependencyContainer(val app: Application) {
val context: Context by lazy { app.applicationContext }
// Drivers
- private val fakeLocationModule: IFakeLocationModule by lazy { FakeLocationModule(app.applicationContext) }
+ private val fakeLocationModule: FakeLocationModule by lazy { FakeLocationModule(app.applicationContext) }
private val permissionsModule by lazy { PermissionsPrivacyModule(app.applicationContext) }
private val ipScramblerModule: IIpScramblerModule by lazy { IpScramblerModule(app.applicationContext) }
diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt
index aa4276d..f7b5439 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/FakeLocationStateUseCase.kt
@@ -28,7 +28,7 @@ import android.util.Log
import foundation.e.privacycentralapp.data.repositories.LocalStateRepository
import foundation.e.privacycentralapp.domain.entities.LocationMode
import foundation.e.privacycentralapp.dummy.CityDataSource
-import foundation.e.privacymodules.location.IFakeLocationModule
+import foundation.e.privacymodules.fakelocation.FakeLocationModule
import foundation.e.privacymodules.permissions.PermissionsPrivacyModule
import foundation.e.privacymodules.permissions.data.AppOpModes
import foundation.e.privacymodules.permissions.data.ApplicationDescription
@@ -39,7 +39,7 @@ import kotlinx.coroutines.launch
import kotlin.random.Random
class FakeLocationStateUseCase(
- private val fakeLocationModule: IFakeLocationModule,
+ private val fakeLocationModule: FakeLocationModule,
private val permissionsModule: PermissionsPrivacyModule,
private val localStateRepository: LocalStateRepository,
private val citiesRepository: CityDataSource,
@@ -61,23 +61,15 @@ class FakeLocationStateUseCase(
private val locationManager: LocationManager
get() = appContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager
- private fun acquireLocationPermission() {
- if (appContext.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
- permissionsModule.toggleDangerousPermission(
- appDesc,
- android.Manifest.permission.ACCESS_FINE_LOCATION,
- true
- )
- }
+ private fun hasAcquireLocationPermission(): Boolean {
+ return (appContext.checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED)
+ || permissionsModule.toggleDangerousPermission(appDesc, android.Manifest.permission.ACCESS_FINE_LOCATION, true)
}
private fun applySettings(isQuickPrivacyEnabled: Boolean, fakeLocation: Pair<Float, Float>?, isSpecificLocation: Boolean = false) {
_configuredLocationMode.value = computeLocationMode(fakeLocation, isSpecificLocation)
- if (isQuickPrivacyEnabled && fakeLocation != null) {
- if (permissionsModule.getAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION) != AppOpModes.ALLOWED) {
- permissionsModule.setAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpModes.ALLOWED)
- }
+ if (isQuickPrivacyEnabled && fakeLocation != null && hasAcquireMockLocationPermission()) {
fakeLocationModule.startFakeLocation()
fakeLocationModule.setFakeLocation(fakeLocation.first.toDouble(), fakeLocation.second.toDouble())
localStateRepository.locationMode.value = configuredLocationMode.value.first
@@ -87,6 +79,11 @@ class FakeLocationStateUseCase(
}
}
+ private fun hasAcquireMockLocationPermission(): Boolean {
+ return (permissionsModule.getAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION) == AppOpModes.ALLOWED)
+ || permissionsModule.setAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpModes.ALLOWED)
+ }
+
fun setSpecificLocation(latitude: Float, longitude: Float) {
if (!localStateRepository.isQuickPrivacyEnabled) {
localStateRepository.setShowQuickPrivacyDisabledMessage(true)
@@ -161,8 +158,11 @@ class FakeLocationStateUseCase(
}
}
- fun startListeningLocation() {
- requestLocationUpdates(localListener)
+ fun startListeningLocation(): Boolean {
+ return if (hasAcquireLocationPermission()) {
+ requestLocationUpdates(localListener)
+ true
+ } else false
}
fun stopListeningLocation() {
@@ -170,7 +170,6 @@ class FakeLocationStateUseCase(
}
fun requestLocationUpdates(listener: LocationListener) {
- acquireLocationPermission()
try {
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER, // TODO: tight this with fakelocation module.
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt
index 2b858e9..d98cb5d 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationFragment.kt
@@ -17,6 +17,7 @@
package foundation.e.privacycentralapp.features.location
+import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.location.Location
@@ -24,6 +25,7 @@ import android.os.Bundle
import android.text.Editable
import android.view.View
import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.NonNull
import androidx.core.view.isVisible
import androidx.core.widget.addTextChangedListener
@@ -81,6 +83,16 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
private var inputJob: Job? = null
+ private val locationPermissionRequest = registerForActivityResult(
+ ActivityResultContracts.RequestMultiplePermissions()
+ ) { permissions ->
+ if (permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false)
+ || permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false)
+ ) {
+ viewModel.submitAction(Action.StartListeningLocation)
+ } // TODO: else.
+ }
+
companion object {
private const val DEBOUNCE_PERIOD = 1000L
}
@@ -147,6 +159,13 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
is FakeLocationViewModel.SingleEvent.LocationUpdatedEvent -> {
updateLocation(event.location, event.mode)
}
+ is FakeLocationViewModel.SingleEvent.RequestLocationPermission -> {
+ // TODO for standalone: rationale dialog
+ locationPermissionRequest.launch(arrayOf(
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.ACCESS_COARSE_LOCATION
+ ))
+ }
}
}
}
@@ -326,13 +345,13 @@ class FakeLocationFragment : NavToolbarFragment(R.layout.fragment_fake_location)
override fun onResume() {
super.onResume()
- viewModel.submitAction(Action.EnterScreen)
+ viewModel.submitAction(Action.StartListeningLocation)
binding.mapView.onResume()
}
override fun onPause() {
super.onPause()
- viewModel.submitAction(Action.LeaveScreen)
+ viewModel.submitAction(Action.StopListeningLocation)
binding.mapView.onPause()
}
diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt
index af20a72..afba3d0 100644
--- a/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt
+++ b/app/src/main/java/foundation/e/privacycentralapp/features/location/FakeLocationViewModel.kt
@@ -86,8 +86,8 @@ class FakeLocationViewModel(
fun submitAction(action: Action) = viewModelScope.launch {
when (action) {
- is Action.EnterScreen -> fakeLocationStateUseCase.startListeningLocation()
- is Action.LeaveScreen -> fakeLocationStateUseCase.stopListeningLocation()
+ is Action.StartListeningLocation -> actionStartListeningLocation()
+ is Action.StopListeningLocation -> fakeLocationStateUseCase.stopListeningLocation()
is Action.SetSpecificLocationAction -> setSpecificLocation(action)
is Action.UseRandomLocationAction -> fakeLocationStateUseCase.setRandomLocation()
is Action.UseRealLocationAction ->
@@ -97,18 +97,26 @@ class FakeLocationViewModel(
}
}
+ private suspend fun actionStartListeningLocation() {
+ val started = fakeLocationStateUseCase.startListeningLocation()
+ if (!started) {
+ _singleEvents.emit(SingleEvent.RequestLocationPermission)
+ }
+ }
+
private suspend fun setSpecificLocation(action: Action.SetSpecificLocationAction) {
specificLocationInputFlow.emit(action)
}
sealed class SingleEvent {
data class LocationUpdatedEvent(val mode: LocationMode, val location: Location?) : SingleEvent()
+ object RequestLocationPermission: SingleEvent()
data class ErrorEvent(val error: String) : SingleEvent()
}
sealed class Action {
- object EnterScreen : Action()
- object LeaveScreen : Action()
+ object StartListeningLocation : Action()
+ object StopListeningLocation : Action()
object UseRealLocationAction : Action()
object UseRandomLocationAction : Action()
data class SetSpecificLocationAction(
diff --git a/app/src/standalone/res/values-night/colors.xml b/app/src/standalone/res/values-night/colors.xml
new file mode 100644
index 0000000..079b968
--- /dev/null
+++ b/app/src/standalone/res/values-night/colors.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 E FOUNDATION
+ ~
+ ~ This program is free software: you can redistribute it and/or modify
+ ~ it under the terms of the GNU General Public License as published by
+ ~ the Free Software Foundation, either version 3 of the License, or
+ ~ (at your option) any later version.
+ ~
+ ~ This program is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ ~ GNU General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU General Public License
+ ~ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ -->
+
+<resources>
+ <!--themable -->
+ <color name="primary">#272727</color>
+ <color name="accent">#5DB2FF</color>
+
+ <color name="primary_text">#CCFFFFFF</color>
+ <color name="secondary_text">#8CFFFFFF</color>
+
+ <color name="background">#121212</color>
+
+</resources> \ No newline at end of file
diff --git a/app/src/standalone/res/values/colors.xml b/app/src/standalone/res/values/colors.xml
new file mode 100644
index 0000000..bd27922
--- /dev/null
+++ b/app/src/standalone/res/values/colors.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 E FOUNDATION
+ ~
+ ~ This program is free software: you can redistribute it and/or modify
+ ~ it under the terms of the GNU General Public License as published by
+ ~ the Free Software Foundation, either version 3 of the License, or
+ ~ (at your option) any later version.
+ ~
+ ~ This program is distributed in the hope that it will be useful,
+ ~ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ ~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ ~ GNU General Public License for more details.
+ ~
+ ~ You should have received a copy of the GNU General Public License
+ ~ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ -->
+
+<resources>
+ <!--themable -->
+ <color name="primary">#FFFFFF</color>
+ <color name="accent">#0086FF</color>
+
+ <color name="primary_text">#CC000000</color>
+ <color name="secondary_text">#8C000000</color>
+
+ <color name="background">#FAFAFA</color>
+</resources> \ No newline at end of file
diff --git a/app/src/standalone/res/values/strings.xml b/app/src/standalone/res/values/strings.xml
new file mode 100644
index 0000000..7719e7b
--- /dev/null
+++ b/app/src/standalone/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+ <string name="app_name">A-P - Standalone</string>
+</resources>