Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
# Java class files
*.class

# Built libraries
*.so
*.jar

# Generated files
bin/
gen/
Expand Down Expand Up @@ -64,7 +68,10 @@ captures/
.externalNativeBuild

# Google Services (e.g. APIs or Firebase)
# google-services.json
# Never commit this: OpenCPN-builder drops a placeholder here for ENV=dev, and a
# real one would be bdbcat's Firebase credentials. Committing either would make
# ENV=prod silently build against whatever happened to be checked in.
google-services.json

# Freeline
freeline.py
Expand Down
6 changes: 5 additions & 1 deletion afilechooser2/build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
apply plugin: 'com.android.library'

android {
compileSdk 34
// Kept in step with app/: one compileSdk across the project means the
// builder image needs one SDK platform, not three. Compile-time only —
// targetSdk below is what governs runtime behaviour, and for a library
// module even that is overridden by the consuming app.
compileSdk 35


defaultConfig {
Expand Down
402 changes: 172 additions & 230 deletions app/build.gradle

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions app/src/debug/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<manifest xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<!-- Disable Firebase in debug builds: the bundled google-services.json
ships a placeholder API key, so FirebaseInitProvider crashes at
startup ("Please set a valid API key"). Removing the auto-init
provider keeps debug builds Firebase-free. Release builds are
unaffected (they get a real key injected in CI). -->
<provider
android:name="com.google.firebase.provider.FirebaseInitProvider"
android:authorities="${applicationId}.firebaseinitprovider"
tools:node="remove" />
</application>
</manifest>
19 changes: 19 additions & 0 deletions app/src/debug/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Debug-only overrides. Android merges the debug source set over main/, so these
win for `assembleDebug` and leave the release build untouched.

Both strings are hardcoded to the base package in main/, not derived from
applicationId, so the ".debug" applicationIdSuffix alone would not move them:

- file_provider_authority: a provider authority must be unique across the whole
device. Stock OpenCPN declares the same one, so without this the debug build
fails to install (INSTALL_FAILED_CONFLICTING_PROVIDER).

- app_name: so the two are distinguishable in the launcher and task switcher
rather than both reading "OpenCPN".
-->
<resources>
<string name="app_name">OpenCPN (debug)</string>
<string name="file_provider_authority" translatable="false">org.opencpn.opencpn.debug.helpfile.fileprovider</string>
</resources>
7 changes: 4 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,13 @@
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package -->
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="1"/>
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
<!-- Run with local libs -->
<meta-data android:name="android.app.use_local_qt_libs" android:value="1"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<meta-data android:name="android.app.load_local_libs" android:value="plugins/platforms/android/libqtforandroid.so"/>
<!-- Qt 5.14+ multi-ABI loader: plugins come from @array/load_local_libs
("<abi>;<lib.so>:..."), replacing 5.12's bundled_in_lib /
bundled_in_assets / load_local_libs(value). See res/values/libs.xml. -->
<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
<meta-data android:name="android.app.load_local_jars" android:value="jar/QtAndroid.jar"/>
<meta-data android:name="android.app.static_init_classes" android:value=""/>
<!-- Messages maps -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5033,10 +5033,13 @@ public void onClick(DialogInterface dialog, int which) {
return;
}

// add all bundled Qt libs to loader params
// add all bundled Qt libs to loader params ("<abi>;<name>" entries;
// keep this device's ABI). For OpenCPN this is the main lib, gorp_<abi>,
// which must be System.load()ed so its exported JNI symbols
// (Java_org_opencpn_OCPNNativeLib_*) resolve.
ArrayList<String> libs = new ArrayList<String>();
if (m_activityInfo.metaData.containsKey("android.app.bundled_libs_resource_id"))
libs.addAll(Arrays.asList(getResources().getStringArray(m_activityInfo.metaData.getInt("android.app.bundled_libs_resource_id"))));
libs.addAll(preferredAbiLibs(getResources().getStringArray(m_activityInfo.metaData.getInt("android.app.bundled_libs_resource_id"))));

// We want the default OCPN plugins bundled into the APK and installed
// into the proper app-lib. So they are listed in ANDROID_EXTRA_LIBS.
Expand All @@ -5048,7 +5051,10 @@ public void onClick(DialogInterface dialog, int which) {

String libName = null;
if (m_activityInfo.metaData.containsKey("android.app.lib_name")) {
libName = m_activityInfo.metaData.getString("android.app.lib_name");
// Qt 5.14+ suffixes the main library with the ABI too:
// lib_name "gorp" -> libgorp_<abi>.so. preferredAbi was fixed by
// the qt_libs pass in startApp() before this method runs.
libName = m_activityInfo.metaData.getString("android.app.lib_name") + "_" + preferredAbi;
loaderParams.putString(MAIN_LIBRARY_KEY, libName); //main library contains main() function
}

Expand Down Expand Up @@ -5440,6 +5446,42 @@ private void cleanOldCacheIfNecessary(String oldLocalPrefix, String localPrefix)
}
}

// Qt 5.14+ ships one multi-ABI Android build: every qt_libs / bundled_libs /
// load_local_libs entry is "<abi>;<name>". This helper (ported from Qt
// 5.15.2's QtLoader) keeps only the running device's preferred ABI and
// returns the bare <name> parts. The first array it processes fixes
// preferredAbi for the rest (main lib, plugins), so qt_libs must go first.
private final java.util.List<String> supportedAbis = java.util.Arrays.asList(Build.SUPPORTED_ABIS);
private String preferredAbi = null;

private ArrayList<String> preferredAbiLibs(String[] libs) {
java.util.HashMap<String, ArrayList<String>> abisLibs = new java.util.HashMap<String, ArrayList<String>>();
for (String lib : libs) {
String[] archLib = lib.split(";", 2);
if (archLib.length != 2)
continue;
if (preferredAbi != null && !archLib[0].equals(preferredAbi))
continue;
if (!abisLibs.containsKey(archLib[0]))
abisLibs.put(archLib[0], new ArrayList<String>());
abisLibs.get(archLib[0]).add(archLib[1]);
}

if (preferredAbi != null) {
if (abisLibs.containsKey(preferredAbi))
return abisLibs.get(preferredAbi);
return new ArrayList<String>();
}

for (String abi : supportedAbis) {
if (abisLibs.containsKey(abi)) {
preferredAbi = abi;
return abisLibs.get(abi);
}
}
return new ArrayList<String>();
}

private void startApp(final boolean firstStart) {
try {
if (m_activityInfo.metaData.containsKey("android.app.qt_sources_resource_id")) {
Expand Down Expand Up @@ -5477,22 +5519,25 @@ private void startApp(final boolean firstStart) {
}


// Set up list of architecture dependent Qt libs to preload
// Set up list of architecture dependent Qt libs to preload. Entries
// are "<abi>;Name_<abi>"; keep this device's ABI and load
// lib<Name_<abi>>.so from the native lib dir. This also fixes
// preferredAbi, used below for the plugins and the main library.
if (m_qtLibs != null) {
for (int i = 0; i < m_qtLibs.length; i++) {
libraryList.add(m_nativeLibraryDir + "/lib" + m_qtLibs[i] + ".so");
}
for (String lib : preferredAbiLibs(m_qtLibs))
libraryList.add(m_nativeLibraryDir + "/lib" + lib + ".so");
}


if (m_activityInfo.metaData.containsKey("android.app.load_local_libs")) {
String[] extraLibs = m_activityInfo.metaData.getString("android.app.load_local_libs").split(":");
for (String lib : extraLibs) {
if (lib.length() > 0) {
if (lib.startsWith("lib/"))
libraryList.add(localPrefix + lib);
else
libraryList.add(pluginsPrefix + lib);
// Local plugins (platform, style, ...). Qt 5.14+ stores them as a
// resource array of "<abi>;<lib.so>[:<lib.so>...]"; they are packed
// into the APK's lib dir (jniLibs) under their real suffixed names.
if (m_activityInfo.metaData.containsKey("android.app.load_local_libs_resource_id")) {
int resId = m_activityInfo.metaData.getInt("android.app.load_local_libs_resource_id");
for (String libs : preferredAbiLibs(getResources().getStringArray(resId))) {
for (String lib : libs.split(":")) {
if (lib.length() > 0)
libraryList.add(m_nativeLibraryDir + "/" + lib);
}
}
}
Expand Down Expand Up @@ -6840,7 +6885,12 @@ public void handleOnBackPressed() {


try {
ApplicationInfo ainfo = this.getApplicationContext().getPackageManager().getApplicationInfo("org.opencpn.opencpn", PackageManager.GET_SHARED_LIBRARY_FILES);
// Look up *this* app's native library dir, not a hardcoded package
// name: with the debug build's ".debug" applicationIdSuffix the
// literal "org.opencpn.opencpn" would resolve to a stock install if
// present (loading its libgorp.so) or throw if not. getPackageName()
// returns the running package, suffix included.
ApplicationInfo ainfo = this.getApplicationContext().getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_SHARED_LIBRARY_FILES);
Log.i("OpenCPN", "native library dir " + ainfo.nativeLibraryDir);
m_nativeLibraryDir = ainfo.nativeLibraryDir;
} catch (Exception e) {
Expand Down
Binary file removed app/src/main/jniLibs/arm64-v8a/libc++_shared.so
Binary file not shown.
Binary file removed app/src/main/jniLibs/armeabi-v7a/libc++_shared.so
Binary file not shown.
82 changes: 51 additions & 31 deletions app/src/main/res/values/libs.xml
Original file line number Diff line number Diff line change
@@ -1,45 +1,65 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<array name="qt_sources">
<item>https://download.qt.io/ministro/android/qt5/qt-5.9</item>
</array>
<!-- Qt 5.15.2 (multi-ABI "android" build) loader format.
==========================================================================
These arrays follow the Qt 5.14+ format that
org.qtproject.qt5.android.bindings.QtLoader parses: every item is
"<abi>;<name>", QtLoader keeps only the running device's preferred ABI,
and turns each qt_libs "<abi>;X" into lib<X>.so. They match the real files
staged by OpenCPN-builder (builder/scripts/install-qt-android.sh) and
bundled by app/build.gradle.

<!-- The following is handled automatically by the deployment tool. It should
not be edited manually. -->
NEEDS the checked-in bindings/*.java (the QtLoader that reads this file)
to be updated from 5.12 to 5.15.2 in lockstep — the 5.12 loader parses
these arrays differently. See TODO.md in OpenCPN-builder. -->

<!--<array name="bundled_libs">
<item>wx_baseu-3.1</item>
<item>wx_baseu_net-3.1</item>
<item>wx_baseu_xml-3.1</item>
<item>wx_qtu_core-3.1</item>
<item>wx_qtu_qa-3.1</item>
<item>wx_qtu_html-3.1</item>
<item>wx_qtu_adv-3.1</item>
<item>wx_qtu_aui-3.1</item>
<item>wx_qtu_gl-3.1</item>
<item>gorp</item>
</array> -->
<array name="qt_sources">
<item>https://download.qt.io/ministro/android/qt5/qt-5.14</item>
</array>

<!-- The main OpenCPN library. It must be listed here (not only as
android.app.lib_name) so QtNative System.load()s it on the Java side.
OpenCPN's JNI entry points use dynamic lookup — exported
Java_org_opencpn_OCPNNativeLib_* symbols (android/androidUTIL.cpp), with no
JNI_OnLoad/RegisterNatives — and the JVM only resolves those for libraries
loaded via System.load, NOT for the main lib that Qt dlopen()s in native
code. Omit it and OCPNNativeLib.onStart throws UnsatisfiedLinkError at
QtActivity.onStart. lib_name (=gorp) still marks it as the main() library.
(OpenSSL and wxWidgets are static inside libgorp, so there are no others.)
The name is gorp_<abi>, matching libgorp_<abi>.so bundled by build.gradle. -->
<array name="bundled_libs">
<item>gorp</item>
<item>arm64-v8a;gorp_arm64-v8a</item>
<item>armeabi-v7a;gorp_armeabi-v7a</item>
</array>

<!-- Qt shared libraries, loaded in dependency order (c++_shared first). Each
"<abi>;Name_<abi>" is loaded as lib<Name_<abi>>.so from the APK lib dir. -->
<array name="qt_libs">
<item>c++_shared</item>
<item>Qt5Core</item>
<item>Qt5Gui</item>
<item>Qt5Widgets</item>
<item>Qt5OpenGL</item>
<item>Qt5Test</item>
<item>Qt5AndroidExtras</item>
</array>

<array name="bundled_in_lib">
<item>libplugins_styles_libqandroidstyle.so:plugins/styles/libqandroidstyle.so</item>
<item>libplugins_platforms_android_libqtforandroid.so:plugins/platforms/android/libqtforandroid.so</item>
<item>arm64-v8a;c++_shared</item>
<item>arm64-v8a;Qt5Core_arm64-v8a</item>
<item>arm64-v8a;Qt5Gui_arm64-v8a</item>
<item>arm64-v8a;Qt5Widgets_arm64-v8a</item>
<item>arm64-v8a;Qt5OpenGL_arm64-v8a</item>
<item>arm64-v8a;Qt5Test_arm64-v8a</item>
<item>arm64-v8a;Qt5AndroidExtras_arm64-v8a</item>
<item>armeabi-v7a;c++_shared</item>
<item>armeabi-v7a;Qt5Core_armeabi-v7a</item>
<item>armeabi-v7a;Qt5Gui_armeabi-v7a</item>
<item>armeabi-v7a;Qt5Widgets_armeabi-v7a</item>
<item>armeabi-v7a;Qt5OpenGL_armeabi-v7a</item>
<item>armeabi-v7a;Qt5Test_armeabi-v7a</item>
<item>armeabi-v7a;Qt5AndroidExtras_armeabi-v7a</item>
</array>

<array name="bundled_in_assets">
<!-- Local plugins, loaded after qt_libs. Each item is
"<abi>;<full-lib-filename>[:<full-lib-filename>...]" — the real Qt 5.15.2
plugin filenames staged into jniLibs by app/build.gradle. (Replaces the
5.12 bundled_in_lib/bundled_in_assets arrays.) The imageformats plugins
(qjpeg/qgif/qico) are also staged and can be appended here if OpenCPN
needs them at runtime. -->
<array name="load_local_libs">
<item>arm64-v8a;libplugins_platforms_qtforandroid_arm64-v8a.so:libplugins_styles_qandroidstyle_arm64-v8a.so</item>
<item>armeabi-v7a;libplugins_platforms_qtforandroid_armeabi-v7a.so:libplugins_styles_qandroidstyle_armeabi-v7a.so</item>
</array>

</resources>
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ ext.kotlin_version = '1.8.22'

allprojects {

// jcenter() removed: the service is shut down, and Gradle fails or stalls on
// it. Everything here resolves from google()/mavenCentral().
repositories {
google()
jcenter()
mavenCentral()
}
}
Expand Down
6 changes: 5 additions & 1 deletion lobsterpicker/build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
apply plugin: 'com.android.library'

android {
compileSdkVersion 33
// Kept in step with app/: one compileSdk across the project means the
// builder image needs one SDK platform, not three. Compile-time only —
// targetSdkVersion below is what governs runtime behaviour, and for a
// library module even that is overridden by the consuming app.
compileSdkVersion 35


defaultConfig {
Expand Down
19 changes: 15 additions & 4 deletions materialfilemanager/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -134,21 +134,32 @@
android:name=".service.ZipService"
android:exported="false" />

<!--
Authorities are ${applicationId}-relative so the debug build (with its
".debug" applicationIdSuffix) does not collide with a stock OpenCPN:
provider authorities must be unique across the whole device, and a
hardcoded "bms.ocpnfilemanager.*" fails to install alongside stock with
INSTALL_FAILED_CONFLICTING_PROVIDER. The manifest merger substitutes
the consuming app's final applicationId. Nothing in this module
resolves against these authorities anyway — the Java CONTENT_URIs point
at the unrelated "bms.myfilemanager.*"/"com.veniosg.dir.*" strings — so
renaming them is inert beyond making the registration unique.
-->
<provider
android:name=".provider.BookmarkProvider"
android:authorities="bms.ocpnfilemanager.bookmarks"
android:authorities="${applicationId}.ocpnfilemanager.bookmarks"
android:exported="false" />
<provider
android:name=".provider.SearchResultsProvider"
android:authorities="bms.ocpnfilemanager.search"
android:authorities="${applicationId}.ocpnfilemanager.search"
android:exported="false" />
<provider
android:name=".provider.SearchSuggestionsProvider"
android:authorities="bms.ocpnfilemanager.search.suggest"
android:authorities="${applicationId}.ocpnfilemanager.search.suggest"
android:exported="false" />
<provider
android:name=".provider.FileManagerProvider"
android:authorities="bms.ocpnfilemanager.filemanager"
android:authorities="${applicationId}.ocpnfilemanager.filemanager"
android:exported="false"
android:permission="android.permission.READ_EXTERNAL_STORAGE" />

Expand Down
6 changes: 4 additions & 2 deletions settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ pluginManagement {
google()
mavenCentral()
gradlePluginPortal()
jcenter()
}
}

include ':app', ':afilechooser', ':afilechooser2', ':lobsterpicker'
// ':afilechooser' is gone -- there is no such directory, and nothing depends on
// it (app uses ':afilechooser2'). Likewise 'FileManager/' is present but is not
// a module here and is not referenced.
include ':app', ':afilechooser2', ':lobsterpicker'

include ':materialfilemanager'