diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml
index 4063b93bfd..149e6a4544 100644
--- a/.github/workflows/android.yml
+++ b/.github/workflows/android.yml
@@ -16,10 +16,10 @@ jobs:
steps:
- uses: actions/checkout@v1
- - name: set up JDK 11
+ - name: set up JDK 17
uses: actions/setup-java@v1
with:
- java-version: 11
+ java-version: 17
- name: Copy dummy keys2.txt
shell: bash
run: |
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 790e9bd3ef..3230502009 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -52,7 +52,11 @@ jobs:
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
-
+ - uses: actions/setup-java@v3
+ with:
+ distribution: 'temurin' # See 'Supported distributions' for available options
+ java-version: '17'
+
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
diff --git a/BUILDING.md b/BUILDING.md
index c17633120b..e5a3a50e85 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -7,7 +7,13 @@ See [translations document](TRANSLATIONS.md) for information on how Vespucci is
## Java
-Building 16.1 and later requires a Java 11 JDK, previous versions assume Java 8.
+Building with gradle requires a Java JDK to be installed.
+
+- 19.1 and later requires Java 17
+- 16.1 - 19.0 Java 11
+- up to 16.0 Java 8
+
+Note that if you are contributing to the code you should restrict yourself only supported Java 8 and Java 11 in Android, in particular the additional de-sugaring provided in later AGP versions doesn't work for Android prior to 5, see https://github.com/MarcusWolschon/osmeditor4android/pull/2131
## Proguard
@@ -15,7 +21,7 @@ All builds now require proguard to be enabled as we have gone over the limit for
## Build flavors
-Due to the forced upgrade policy from google from November 1st 2018 onwards we are now supporting two build flavors: _current_ that will target a recent Android SDK and support library and _legacy_ that will support old Android versions as long as practical.
+Due to the forced upgrade policy from google from November 1st 2018 onwards we supported two build flavors: _current_ that will target a recent Android SDK and support library and _legacy_ that will support old Android versions as long as practical.
For version 15.0 the legacy flavour has been removed as androidx doesn't support versions older than Android 4 and as versions older than 4.1 do not support TLS 1.2 they would be largely non-functional in any case, however flavours may be reactivated if necessary.
@@ -83,8 +89,6 @@ To make running individual tests simpler refreshing the gradle tasks (assuming t
For the on device tests the time to run the tests can be reduced substantially by running against multiple emulators with ``marathonCurrentDebugAndroidTest`` marathon can execute the tests sharded according to the configuration and retry failed tests. An additional bonus is that the test output is much easier to consume and understand. marathon will include a video of failed tests (we migrated from ``spoon`` to `` marathon`` for 16.1). For more information see [https://marathonlabs.github.io/marathon/](https://marathonlabs.github.io/marathon/). On a 20 core/174GB machine running 20 emulators this reduces the run time to between 15 and 20 minutes.
-__Important:__ currently marathon requires requesting and granting the MANAGE_EXTERNAL_STORAGE permission on Android 11 and higher to generate coverage output. A corresponding manifest files is located in src/debug/AndroidManifest.xml with the relevant element commented out. Using this however leads to tests not reflecting the conditions they would be run under in the production app, so you should consider running the tests without the permission during the actual testing and only request it once testing is completed to generate coverage stats.
-
Notes:
* a number of the tests start with the splash screen activity and then wait for the main activity to be started. Experience shows that if one of these fails to complete in certain ways, the following tests that start via the splash screen will not be able to start the main activity. Reason unknown.
diff --git a/build.gradle b/build.gradle
index 2e9e7ca028..44dc28ea28 100644
--- a/build.gradle
+++ b/build.gradle
@@ -17,14 +17,14 @@ buildscript {
}
}
- classpath 'com.android.tools.build:gradle:7.2.2'
+ classpath 'com.android.tools.build:gradle:8.1.0'
classpath 'ch.poole.gradle:markdown-gradle-plugin:0.2.4'
- classpath 'org.jacoco:org.jacoco.core:0.8.7'
+ classpath 'org.jacoco:org.jacoco.core:0.8.9'
classpath "ch.poole:preset-utils:0.26.0"
classpath 'com.github.ksoichiro:gradle-eclipse-aar-plugin:0.3.1'
classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:3.0.1'
- classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.18'
- classpath 'com.malinskiy.marathon:marathon-gradle-plugin:0.8.0'
+ classpath 'com.google.protobuf:protobuf-gradle-plugin:0.9.1'
+ classpath 'com.malinskiy.marathon:marathon-gradle-plugin:0.8.4'
}
}
@@ -220,8 +220,10 @@ ext {
}
android {
-
+ namespace "de.blau.android"
+
defaultConfig {
+ applicationId "de.blau.android"
versionCode project.getVersionCode()
versionName "${project.getVersionName()}"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@@ -253,7 +255,7 @@ android {
}
// this, unluckily, cannot be set per flavour
- compileSdkVersion 31
+ compileSdkVersion 33
// Specifies one flavor dimension.
flavorDimensions "api"
@@ -262,7 +264,7 @@ android {
dimension "api"
multiDexEnabled true
minSdkVersion 16
- targetSdkVersion 31
+ targetSdkVersion 33
applicationIdSuffix ""
versionNameSuffix ""
resValue "string", "content_provider", "de.blau.android.provider"
@@ -276,7 +278,9 @@ android {
testOptions {
unitTests.all {
- jacoco { includeNoLocationClasses = true }
+ jacoco {
+ includeNoLocationClasses = true
+ }
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
@@ -317,6 +321,10 @@ android {
resources.srcDirs += commonTestResources
}
}
+
+ androidResources {
+ generateLocaleConfig true
+ }
}
ext {
@@ -517,6 +525,10 @@ afterEvaluate{
uninstallAll.dependsOn deinstallDummyMeasureApp
}
+ tasks.named('marathonCurrentDebugAndroidTestGenerateMarathonfile') {
+ doNotTrackState('Gradle 8 compatability hack')
+ }
+
// these tasks needs to be created after the uninstall tasks
// hardwired single test, this is only needed during initial developement, once the whole test suite has been run
@@ -579,21 +591,23 @@ dependencies {
implementation "ch.acra:acra-http:$acraVersion"
implementation "ch.acra:acra-dialog:$acraVersion"
// android support and other libraries
- implementation "androidx.activity:activity:1.2.0"
- implementation "androidx.fragment:fragment:1.3.0"
- implementation "androidx.appcompat:appcompat:1.3.0"
+ implementation "androidx.activity:activity:1.2.4"
+ implementation "androidx.fragment:fragment:1.3.6"
+ implementation "androidx.appcompat:appcompat:1.6.1"
+ implementation "androidx.appcompat:appcompat-resources:1.6.1"
implementation "androidx.recyclerview:recyclerview:1.1.0"
implementation "androidx.preference:preference:1.1.0"
implementation "com.google.android.material:material:1.3.0"
implementation "androidx.annotation:annotation:1.1.0"
implementation "androidx.core:core:1.7.0"
- implementation "androidx.exifinterface:exifinterface:1.1.0"
+ implementation "androidx.exifinterface:exifinterface:1.3.6"
implementation "androidx.legacy:legacy-preference-v14:1.0.0"
implementation "androidx.work:work-runtime:$work_version"
implementation "androidx.window:window:1.0.0"
implementation 'androidx.core:core-splashscreen:1.0.0-beta02'
implementation "androidx.multidex:multidex:2.0.1"
implementation "androidx.cardview:cardview:1.0.0"
+ implementation "androidx.viewpager:viewpager:1.1.0-alpha01"
//
implementation 'com.heinrichreimersoftware:android-issue-reporter:1.4'
@@ -602,8 +616,6 @@ dependencies {
implementation 'se.akerfeldt:okhttp-signpost:1.1.0'
implementation "com.squareup.okhttp3:okhttp:$okHttpVersion"
implementation "com.squareup.okhttp3:logging-interceptor:$okHttpVersion"
- implementation "com.adobe.xmp:xmpcore:6.1.11"
- implementation "com.drewnoakes:metadata-extractor:2.16.0"
implementation 'com.google.protobuf:protobuf-java:3.12.2'
implementation "com.google.code.gson:gson:2.8.9"
implementation "com.google.protobuf:protobuf-java:$googleProtobufVersion"
@@ -631,7 +643,6 @@ dependencies {
implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0'
implementation 'io.michaelrocks:libphonenumber-android:8.12.39'
implementation 'io.noties.markwon:core:4.6.2'
- implementation 'com.zeugmasolutions.localehelper:locale-helper-android:1.1.4'
implementation 'com.github.zedlabs:ElementHistoryDialog:1.0.6'
implementation 'com.caverock:androidsvg-aar:1.4'
@@ -642,9 +653,9 @@ dependencies {
testImplementation "junit:junit:4.13.2"
testImplementation 'xmlpull:xmlpull:1.1.3.1'
testImplementation 'net.sf.kxml:kxml2:2.3.0'
- testImplementation 'org.robolectric:robolectric:4.8.1'
- testImplementation 'androidx.test.ext:junit:1.1.3'
- testImplementation 'androidx.test:rules:1.4.0'
+ testImplementation 'org.robolectric:robolectric:4.10.3'
+ testImplementation 'androidx.test.ext:junit:1.1.5'
+ testImplementation 'androidx.test:rules:1.5.0'
testImplementation "com.squareup.okhttp3:mockwebserver:$okHttpVersion"
testImplementation "pl.droidsonroids.yaml:snakeyaml:1.18-android"
testImplementation ("com.orhanobut:mockwebserverplus:1.0.0") {
@@ -653,8 +664,8 @@ dependencies {
testImplementation "androidx.work:work-testing:$work_version"
// Instrumentation tests
- androidTestImplementation 'androidx.test.ext:junit:1.1.3'
- androidTestImplementation 'androidx.test:rules:1.4.0'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation 'androidx.test:rules:1.5.0'
androidTestImplementation "org.hamcrest:hamcrest-library:2.2"
androidTestImplementation "com.squareup.okhttp3:mockwebserver:$okHttpVersion"
androidTestImplementation "pl.droidsonroids.yaml:snakeyaml:1.18-android"
@@ -692,7 +703,15 @@ def coverageSourceDirs = ['src/main/java']
// see https://github.com/gradle/gradle/issues/5184
tasks.withType(Test) {
jacoco.includeNoLocationClasses = true
- jacoco.excludes = ['jdk.internal.*']
+ jacoco.excludes = ['jdk.internal.*', '*']
+ // java 17 reflection workarounds for FST
+ jvmArgs = ["--add-opens","java.base/java.lang=ALL-UNNAMED",
+ "--add-opens", "java.base/java.math=ALL-UNNAMED",
+ "--add-opens", "java.base/java.util=ALL-UNNAMED",
+ "--add-opens", "java.base/java.util.concurrent=ALL-UNNAMED",
+ "--add-opens", "java.base/java.net=ALL-UNNAMED",
+ "--add-opens", "java.base/java.text=ALL-UNNAMED",
+ "--add-opens", "java.base/java.io=ALL-UNNAMED"]
}
task jacocoTestReport(type:JacocoReport, dependsOn: "testCurrentDebugUnitTest") {
@@ -710,8 +729,8 @@ task jacocoTestReport(type:JacocoReport, dependsOn: "testCurrentDebugUnitTest")
dir : "$buildDir",
include : ['outputs/unit_test_code_coverage/currentDebugUnitTest/testCurrentDebugUnitTest.exec', 'outputs/code-coverage/connected/flavors/CURRENT/*coverage.ec', 'reports/marathon/currentDebugAndroidTest/device-files/*/coverage/*.ec'])
reports {
- xml.enabled = true
- html.enabled = true
+ xml.required = true
+ html.required = true
}
sourceDirectories.from = files(coverageSourceDirs)
diff --git a/documentation/docs/help/en/Advanced preferences.md b/documentation/docs/help/en/Advanced preferences.md
index d20c220431..759141cd53 100644
--- a/documentation/docs/help/en/Advanced preferences.md
+++ b/documentation/docs/help/en/Advanced preferences.md
@@ -72,9 +72,9 @@ Select the theme to use. _Follow system_ will follow the setting in the system p
Show the menu buttons at the bottom of the screen. Default: _on_. You need to restart the app for changes to this setting to take effect.
-### Disable translations
+### App language
-Use English for the user interface. Google does not provide a supported way to switch languages for individual apps, as a result this setting relies on multiple workarounds that may, or may not, work on your device. Preset translations can be disabled in the preset configurations.
+Select a language for the user interface that is different from the device default. On devices running Android 13 and later the app language can be changed in the system settings too. Preset translations can be disabled in the preset configurations.
### Max. number of inline values
diff --git a/gradle.properties b/gradle.properties
index 782c51e1ee..ea612aef4d 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,3 +2,6 @@ android.useAndroidX=true
org.gradle.jvmargs=-Xmx6144m
android.enableJetifier=true
android.enableResourceOptimizations=false
+android.nonFinalResIds=false
+android.defaults.buildfeatures.buildconfig=true
+android.nonTransitiveRClass=false
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2e6e5897b5..3a02907943 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/src/androidTest/AndroidManifest.xml b/src/androidTest/AndroidManifest.xml
index 2635080f7b..df62edd638 100644
--- a/src/androidTest/AndroidManifest.xml
+++ b/src/androidTest/AndroidManifest.xml
@@ -7,14 +7,15 @@
-
+
+
tree = new RTree<>(2, 5);
+ index.fill(tree);
+ List photos = new ArrayList<>();
+ tree.query(photos);
+ for (Photo p : photos) {
+ try {
+ main.getContentResolver().delete(p.getRefUri(main), null, null);
+ } catch (SecurityException ex) {
+ //
+ }
+ }
+ }
if (photo1 != null) {
photo1.delete();
}
diff --git a/src/androidTest/java/de/blau/android/prefs/GeocoderPrefTest.java b/src/androidTest/java/de/blau/android/prefs/GeocoderPrefTest.java
index 8115fc60bf..d39d2763e8 100644
--- a/src/androidTest/java/de/blau/android/prefs/GeocoderPrefTest.java
+++ b/src/androidTest/java/de/blau/android/prefs/GeocoderPrefTest.java
@@ -108,7 +108,8 @@ public void geocoder() {
fail(e.getMessage());
}
assertTrue(TestUtils.clickText(device, false, main.getString(R.string.okay), true, false));
- assertTrue(TestUtils.clickHome(device, true));
+ TestUtils.clickHome(device, false);
+ TestUtils.clickHome(device, false);
try (AdvancedPrefDatabase prefDb = new AdvancedPrefDatabase(main)) {
Geocoder[] geocoders = prefDb.getGeocoders();
assertEquals(3, geocoders.length);
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 113ef30baa..bfc272de4f 100755
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -25,9 +25,10 @@
-
+
+
@@ -310,6 +311,17 @@
android:configChanges="orientation|screenSize|keyboardHidden|density|screenLayout|uiMode|fontScale"
android:foregroundServiceType="location"
android:label="TrackerService" />
+
+
+
+
+
+
= Build.VERSION_CODES.TIRAMISU ? Manifest.permission.READ_MEDIA_IMAGES
+ : Manifest.permission.WRITE_EXTERNAL_STORAGE;
+
/**
* Minimum change in azimuth before we redraw
*/
@@ -920,18 +926,18 @@ private void checkPermissions(@NonNull Runnable whenDone) {
}
}
synchronized (storagePermissionLock) {
- if (!Util.permissionGranted(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ if (!Util.permissionGranted(this, STORAGE_PERMISSION)
|| (prefs.scanMediaStore() && !Util.permissionGranted(this, Manifest.permission.ACCESS_MEDIA_LOCATION))) {
storagePermissionGranted = false;
// Should we show an explanation?
if (askedForStoragePermission) {
- if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ if (ActivityCompat.shouldShowRequestPermissionRationale(this, STORAGE_PERMISSION)) {
// for now we just repeat the request (max once)
- permissionsList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ permissionsList.add(STORAGE_PERMISSION);
permissionsList.add(Manifest.permission.ACCESS_MEDIA_LOCATION);
}
} else {
- permissionsList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ permissionsList.add(STORAGE_PERMISSION);
permissionsList.add(Manifest.permission.ACCESS_MEDIA_LOCATION); // yes this is weird, but ask the
// goog
askedForStoragePermission = true;
@@ -953,8 +959,8 @@ private void checkPermissions(@NonNull Runnable whenDone) {
*/
private void getIntentData() {
synchronized (newIntentsLock) {
- geoData = (GeoUrlData) getIntent().getSerializableExtra(GeoUrlActivity.GEODATA);
- rcData = (RemoteControlUrlData) getIntent().getSerializableExtra(RemoteControlUrlActivity.RCDATA);
+ geoData = Util.getSerializableExtra(getIntent(), GeoUrlActivity.GEODATA, GeoUrlData.class);
+ rcData = Util.getSerializableExtra(getIntent(), RemoteControlUrlActivity.RCDATA, RemoteControlUrlData.class);
shortcutExtras = getIntent().getBundleExtra(Splash.SHORTCUT_EXTRAS_KEY);
Uri uri = getIntent().getData();
contentUriType = getIntent().getType();
@@ -965,7 +971,7 @@ private void getIntentData() {
Bundle extras = getIntent().getExtras();
if (extras != null) {
try {
- Uri streamUri = (Uri) extras.getParcelable(Intent.EXTRA_STREAM);
+ Uri streamUri = Util.getParcelable(extras, Intent.EXTRA_STREAM, Uri.class);
Log.d(DEBUG_TAG, "getIntentData EXTRA_STREAM " + streamUri);
if (streamUri != null) {
contentUri = streamUri;
@@ -1041,7 +1047,7 @@ private void processIntents() {
case ACTION_PUSH_SELECTION:
case ACTION_POP_SELECTION:
if (ACTION_PUSH_SELECTION.equals(action)) {
- Selection.Ids ids = (Ids) intent.getSerializableExtra(Selection.SELECTION_KEY);
+ Selection.Ids ids = Util.getSerializableExtra(intent, Selection.SELECTION_KEY, Ids.class);
Selection selection = new Selection();
selection.fromIds(App.getDelegator(), ids);
logic.pushSelection(selection);
@@ -1494,7 +1500,7 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis
locationPermissionGranted = true;
}
} // if not granted do nothing for now
- if (permissions[i].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE) && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ if (permissions[i].equals(STORAGE_PERMISSION) && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted :)
synchronized (storagePermissionLock) {
storagePermissionGranted = true;
diff --git a/src/main/java/de/blau/android/layer/photos/MapOverlay.java b/src/main/java/de/blau/android/layer/photos/MapOverlay.java
index f80f8785ec..84c949600d 100644
--- a/src/main/java/de/blau/android/layer/photos/MapOverlay.java
+++ b/src/main/java/de/blau/android/layer/photos/MapOverlay.java
@@ -4,7 +4,6 @@
import java.util.List;
import java.util.concurrent.ExecutorService;
-import android.Manifest;
import android.content.Context;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -24,6 +23,7 @@
import androidx.fragment.app.FragmentActivity;
import de.blau.android.App;
import de.blau.android.Logic;
+import de.blau.android.Main;
import de.blau.android.Map;
import de.blau.android.PostAsyncActionHandler;
import de.blau.android.R;
@@ -181,7 +181,7 @@ public boolean isReadyToDraw() {
@Override
protected void onDraw(Canvas c, IMapView osmv) {
if (isVisible) {
- if (needsIndexing() && Util.permissionGranted(map.getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ if (needsIndexing() && Util.permissionGranted(map.getContext(), Main.STORAGE_PERMISSION)) {
indexer.execute(map::invalidate);
return;
}
diff --git a/src/main/java/de/blau/android/photos/Photo.java b/src/main/java/de/blau/android/photos/Photo.java
index e41086d230..851ea58f6b 100644
--- a/src/main/java/de/blau/android/photos/Photo.java
+++ b/src/main/java/de/blau/android/photos/Photo.java
@@ -2,6 +2,7 @@
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.util.Objects;
import android.content.Context;
@@ -14,7 +15,6 @@
import de.blau.android.R;
import de.blau.android.osm.BoundingBox;
import de.blau.android.osm.GeoPoint;
-import de.blau.android.util.ExtendedExifInterface;
import de.blau.android.util.Util;
import de.blau.android.util.rtree.BoundedObject;
@@ -50,7 +50,28 @@ public class Photo implements BoundedObject, GeoPoint {
* @throws NumberFormatException If there was a problem parsing the XML.
*/
public Photo(@NonNull Context context, @NonNull Uri uri, @Nullable String displayName) throws IOException, NumberFormatException {
- this(new ExtendedExifInterface(context, uri), uri.toString(), displayName);
+ this(new ExifInterface(openInputStream(context, uri)), uri.toString(), displayName);
+ }
+
+ /**
+ * Get an InputStream from an uri
+ *
+ * @param context Android context
+ * @param uri a content or file uri
+ * @return an InputStream
+ * @throws IOException if anything goes wrong
+ */
+ @NonNull
+ private static InputStream openInputStream(@NonNull Context context, @NonNull Uri uri) throws IOException {
+ try {
+ return context.getContentResolver().openInputStream(uri);
+ } catch (Exception ex) {
+ // other stuff broken ... for example ArrayIndexOutOfBounds
+ throw new IOException(ex.getMessage());
+ } catch (Error err) { // NOSONAR crashing is not an option
+ // other stuff broken ... for example NoSuchMethodError
+ throw new IOException(err.getMessage());
+ }
}
/**
@@ -62,7 +83,7 @@ public Photo(@NonNull Context context, @NonNull Uri uri, @Nullable String displa
* @throws NumberFormatException If there was a problem parsing the XML.
*/
public Photo(@NonNull File directory, @NonNull File imageFile) throws IOException, NumberFormatException {
- this(new ExtendedExifInterface(imageFile.toString()), imageFile.getAbsolutePath(), imageFile.getName());
+ this(new ExifInterface(imageFile.toString()), imageFile.getAbsolutePath(), imageFile.getName());
}
/**
@@ -73,7 +94,7 @@ public Photo(@NonNull File directory, @NonNull File imageFile) throws IOExceptio
* @param displayName a name of the image for display purposes
* @throws IOException if location information is missing
*/
- private Photo(@NonNull ExtendedExifInterface exif, @NonNull String ref, @Nullable String displayName) throws IOException {
+ private Photo(@NonNull ExifInterface exif, @NonNull String ref, @Nullable String displayName) throws IOException {
this.ref = ref;
this.displayName = displayName;
@@ -87,14 +108,14 @@ private Photo(@NonNull ExtendedExifInterface exif, @NonNull String ref, @Nullabl
float lonf = convertToDegree(lonStr);
String lonRef = exif.getAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF);
- if (lonRef != null && !ExtendedExifInterface.EAST.equals(lonRef)) { // deal with the negative degrees
+ if (lonRef != null && !ExifInterface.LONGITUDE_EAST.equals(lonRef)) { // deal with the negative degrees
lonf = -lonf;
}
float latf = convertToDegree(exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE));
String latRef = exif.getAttribute(ExifInterface.TAG_GPS_LATITUDE_REF);
- if (latRef != null && !ExtendedExifInterface.NORTH.equals(latRef)) {
+ if (latRef != null && !ExifInterface.LATITUDE_NORTH.equals(latRef)) {
latf = -latf;
}
if (!(Util.notZero(lonf) && Util.notZero(latf))) {
@@ -104,11 +125,15 @@ private Photo(@NonNull ExtendedExifInterface exif, @NonNull String ref, @Nullabl
lat = (int) (latf * 1E7d);
lon = (int) (lonf * 1E7d);
Log.d(DEBUG_TAG, "lat: " + lat + " lon: " + lon);
-
- String dir = exif.getAttribute(ExtendedExifInterface.TAG_GPS_IMG_DIRECTION);
+ String dir = exif.getAttribute(ExifInterface.TAG_GPS_IMG_DIRECTION);
if (dir != null) {
- direction = (int) Double.parseDouble(dir);
- directionRef = exif.getAttribute(ExtendedExifInterface.TAG_GPS_IMG_DIRECTION_REF);
+ String[] r = dir.split("/");
+ if (r.length != 2) {
+ return;
+ }
+ direction = (int) (Double.valueOf(r[0]) / Double.valueOf(r[1]));
+ Log.d(DEBUG_TAG, ExifInterface.TAG_GPS_IMG_DIRECTION + " " + dir);
+ directionRef = exif.getAttribute(ExifInterface.TAG_GPS_IMG_DIRECTION_REF);
Log.d(DEBUG_TAG, "dir " + dir + " direction " + direction + " ref " + directionRef);
}
}
@@ -141,7 +166,7 @@ public Photo(int lat, int lon, int direction, @NonNull String ref, @Nullable Str
this.lat = lat;
this.lon = lon;
this.direction = direction;
- this.directionRef = ExtendedExifInterface.MAGNETIC_NORTH;
+ this.directionRef = ExifInterface.GPS_DIRECTION_MAGNETIC;
this.ref = ref;
this.displayName = displayName;
}
diff --git a/src/main/java/de/blau/android/photos/PhotoIndex.java b/src/main/java/de/blau/android/photos/PhotoIndex.java
index 30e5724823..f484762f9d 100644
--- a/src/main/java/de/blau/android/photos/PhotoIndex.java
+++ b/src/main/java/de/blau/android/photos/PhotoIndex.java
@@ -46,7 +46,8 @@
public class PhotoIndex extends SQLiteOpenHelper {
private static final int DATA_VERSION = 6;
- private static final String DEBUG_TAG = "PhotoIndex";
+ public static final String DB_NAME = PhotoIndex.class.getSimpleName();
+ private static final String DEBUG_TAG = DB_NAME;
private static final String NOVESPUCCI = ".novespucci";
@@ -291,16 +292,15 @@ private void indexDirectories() {
} finally {
close(dbresult2);
}
- } else {
- Log.d(DEBUG_TAG, "Directory " + indir.getAbsolutePath() + " doesn't exist");
- // remove all entries for this directory
- db.delete(PHOTOS_TABLE, URI_WHERE, new String[] { indir.getAbsolutePath() });
- db.delete(PHOTOS_TABLE, "dir LIKE ?", new String[] { indir.getAbsolutePath() + "/%" });
+ continue;
}
+ Log.d(DEBUG_TAG, "Directory " + indir.getAbsolutePath() + " doesn't exist");
+ // remove all entries for this directory
+ db.delete(PHOTOS_TABLE, URI_WHERE, new String[] { indir.getAbsolutePath() });
+ db.delete(PHOTOS_TABLE, "dir LIKE ?", new String[] { indir.getAbsolutePath() + "/%" });
}
dbresult.moveToNext();
}
-
} catch (SQLiteException ex) {
// Don't crash just report
ACRAHelper.nocrashReport(ex, ex.getMessage());
diff --git a/src/main/java/de/blau/android/prefs/AdvancedPrefEditorFragment.java b/src/main/java/de/blau/android/prefs/AdvancedPrefEditorFragment.java
index cf945488b3..b875a56c0d 100644
--- a/src/main/java/de/blau/android/prefs/AdvancedPrefEditorFragment.java
+++ b/src/main/java/de/blau/android/prefs/AdvancedPrefEditorFragment.java
@@ -5,18 +5,16 @@
import java.util.List;
import java.util.Locale;
-import com.zeugmasolutions.localehelper.LocaleAwareCompatActivity;
-
-import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.NonNull;
-import androidx.preference.CheckBoxPreference;
+import androidx.appcompat.app.AppCompatDelegate;
+import androidx.core.os.LocaleListCompat;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
-import androidx.preference.PreferenceManager;
+import androidx.preference.Preference.OnPreferenceChangeListener;
import de.blau.android.R;
import de.blau.android.util.LocaleUtils;
import de.blau.android.util.Util;
@@ -63,6 +61,11 @@ public void onResume() {
setupCameraPref(cameraAppPref);
}
+ ListPreference appLocalePref = (ListPreference) getPreferenceScreen().findPreference(r.getString(R.string.config_appLocale_key));
+ if (appLocalePref != null) {
+ setupAppLocalePref(appLocalePref);
+ }
+
setListPreferenceSummary(R.string.config_selectCameraApp_key, false);
setListPreferenceSummary(R.string.config_theme_key, true);
setListPreferenceSummary(R.string.config_fullscreenMode_key, true);
@@ -87,6 +90,43 @@ public void onResume() {
setTitle();
}
+ /**
+ * Setup the app local preference
+ *
+ * @param appLocalePref the preference
+ *
+ */
+ private void setupAppLocalePref(@NonNull ListPreference appLocalePref) {
+ Locale currentLocale = Locale.getDefault();
+ LocaleListCompat appLocales = LocaleUtils.getSupportedLocales(getContext());
+ LocaleListCompat currentLocales = AppCompatDelegate.getApplicationLocales();
+ if (!currentLocales.isEmpty()) {
+ LocaleListCompat temp = LocaleListCompat.getAdjustedDefault();
+ if (!temp.isEmpty()) {
+ currentLocale = temp.get(0);
+ }
+ }
+ String[] entries = new String[appLocales.size()];
+ String[] values = new String[appLocales.size()];
+ for (int i = 0; i < appLocales.size(); i++) {
+ Locale l = appLocales.get(i);
+ entries[i] = l.getDisplayName(currentLocale);
+ values[i] = l.toString();
+ }
+ appLocalePref.setEntryValues(values);
+ appLocalePref.setEntries(entries);
+ appLocalePref.setDefaultValue(currentLocale.toString());
+ OnPreferenceChangeListener p = (preference, newValue) -> {
+ Log.d(DEBUG_TAG, "onPreferenceChange appLocale " + newValue);
+ LocaleListCompat newDefaultList = LocaleListCompat.forLanguageTags((String) newValue);
+ Locale newDefault = newDefaultList.get(0);
+ AppCompatDelegate.setApplicationLocales(newDefaultList);
+ preference.setSummary(newDefault.getDisplayName(newDefault));
+ return true;
+ };
+ appLocalePref.setOnPreferenceChangeListener(p);
+ }
+
/**
* Setup the possible camera apps for selection
*
@@ -134,23 +174,5 @@ private void setOnPreferenceClickListeners() {
return true;
});
}
-
- Preference disableTranslationsPref = getPreferenceScreen().findPreference(r.getString(R.string.config_disableTranslations_key));
- if (disableTranslationsPref != null) {
- disableTranslationsPref.setOnPreferenceClickListener(preference -> {
- LocaleAwareCompatActivity lac = ((LocaleAwareCompatActivity) getActivity());
- String savedLocaleKey = lac.getString(R.string.config_savedLocale_key);
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(lac);
- if (((CheckBoxPreference) disableTranslationsPref).isChecked()) {
- if (!prefs.contains(savedLocaleKey)) {
- prefs.edit().putString(savedLocaleKey, LocaleUtils.toLanguageTag(Locale.getDefault())).commit();
- }
- lac.updateLocale(Locale.ENGLISH);
- } else {
- lac.updateLocale(LocaleUtils.forLanguageTag(prefs.getString(savedLocaleKey, Locale.ENGLISH.toString())));
- }
- return true;
- });
- }
}
}
diff --git a/src/main/java/de/blau/android/prefs/VespucciURLActivity.java b/src/main/java/de/blau/android/prefs/VespucciURLActivity.java
index 32bb6c29d6..74a53cb9fe 100644
--- a/src/main/java/de/blau/android/prefs/VespucciURLActivity.java
+++ b/src/main/java/de/blau/android/prefs/VespucciURLActivity.java
@@ -4,8 +4,6 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
-import com.zeugmasolutions.localehelper.LocaleAwareCompatActivity;
-
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -19,6 +17,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
+import androidx.appcompat.app.AppCompatActivity;
import de.blau.android.Authorize;
import de.blau.android.R;
import de.blau.android.net.OAuthHelper;
@@ -41,7 +40,7 @@
* @author Simon
*
*/
-public class VespucciURLActivity extends LocaleAwareCompatActivity implements OnClickListener {
+public class VespucciURLActivity extends AppCompatActivity implements OnClickListener {
private static final String DEBUG_TAG = VespucciURLActivity.class.getSimpleName();
private static final int REQUEST_PRESETEDIT = 0;
diff --git a/src/main/java/de/blau/android/propertyeditor/PropertyEditorActivity.java b/src/main/java/de/blau/android/propertyeditor/PropertyEditorActivity.java
index a7ebe8e37b..c7c33a3f43 100644
--- a/src/main/java/de/blau/android/propertyeditor/PropertyEditorActivity.java
+++ b/src/main/java/de/blau/android/propertyeditor/PropertyEditorActivity.java
@@ -6,8 +6,6 @@
import org.acra.ACRA;
-import com.zeugmasolutions.localehelper.LocaleAwareCompatActivity;
-
import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
@@ -17,6 +15,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
@@ -45,7 +44,7 @@
* @author simon
*/
public class PropertyEditorActivity & Serializable, L extends List & Serializable, T extends List