diff --git a/build-android/README.md b/build-android/README.md
index 2808d9a4e..86961cb8f 100644
--- a/build-android/README.md
+++ b/build-android/README.md
@@ -1,93 +1,124 @@
-# Binary
+# Building MailCore2 for Android
-Download the latest [build for Android](http://d.etpan.org/mailcore2-deps/mailcore2-android/) from 2016. Or build mailcore on Android:
+This builds MailCore2 into `mailcore2-android-4.aar` for **NDK 26 / libc++**
+(the original 2016 gnustl/NDK-17 chain is gone). The result is a single
+self-contained, 16 KB-page-aligned `libMailCore.so` per ABI plus the Java
+`com.libmailcore` API.
-# Build for Android
+There is no maintained prebuilt to download — build your own as below.
-The below instruction allows to build mailcore with its needed dependencies on macOS (when followed exactly in this order).
+## Prerequisites
-## Compile libetpan
-- Download https://github.com/dinhvh/libetpan/releases/tag/1.9.4 in version 1.9.4 (which fixes exception in QUOTA operation): Make sure in this libetpan path are not spaces, this causes random compile errors
-- Download Android NDK 17
-- Download Android SDK 16 and SDK 21
-- Set ENV-vars to your NDK/SDK root folder
-- Set ENV-vars to your NDK/SDK root folder
+- **Android NDK r26.3+** (r23+ is required on Apple-Silicon Mac hosts). Tested
+ with `26.3.11579264`. Set `ANDROID_NDK` to it.
+- **Android SDK** with platform **android-16** (used only by the `javac`
+ classpath step) and build-tools. Set `ANDROID_SDK`.
+- **CMake** (generates the `MailCore/*.h` umbrella headers) and **JDK 17**.
+- For the ctemplate build (step 1b): **Python 3** (with `2to3`) and `curl`.
+
+```sh
+export ANDROID_NDK=$HOME/Library/Android/sdk/ndk/26.3.11579264
+export ANDROID_SDK=$HOME/Library/Android/sdk
+```
+
+## Step 1 — build the native dependencies
+
+MailCore links eight native libraries. Five must be **rebuilt** so they match
+NDK 26 / libc++ / OpenSSL 1.1.x; three legacy C libraries are still fetched from
+`d.etpan.org` automatically and link fine as-is.
+
+**1a. From the libetpan repo** — `libetpan` plus the deps it links. Build them
+once in the [libetpan `build-android`](https://github.com/dinhvh/libetpan)
+(see its README); reusing libetpan's exact OpenSSL/SASL/iconv keeps the ABI
+consistent:
+
+| Zip | Version | Built by |
+|-----|---------|----------|
+| `libetpan-android-7.zip` | 1.9.x | `libetpan/build-android/build.sh` |
+| `openssl-android-3.zip` | OpenSSL 1.1.1w | ↑ (same run) |
+| `cyrus-sasl-android-4.zip` | 2.1.28 | ↑ (same run) |
+| `iconv-android-1.zip` | libiconv 1.15 | ↑ (same run) |
+
+**1b. ctemplate — built here** (it is a MailCore-only dependency; libetpan does
+not use it):
+
+```sh
+export ANDROID_NDK=$HOME/Library/Android/sdk/ndk/26.3.11579264
+cd build-android/dependencies/ctemplate
+./build.sh # → ctemplate-android-3.zip (needs Python 3 for the fsm headers)
+```
+
+> The `d.etpan.org` copies of `ctemplate`, `openssl`, `cyrus-sasl` and
+> `libetpan` are the **old gnustl / OpenSSL-1.0.2 builds and will not link** — you
+> must supply the rebuilt zips above. `icu4c-android-3`, `libxml2-android-3` and
+> `tidy-html5-android-3` are plain C/C++ libs that still work and are downloaded
+> for you.
+
+## Step 2 — stage the dependencies
+
+Put all five rebuilt zips into `build-android/third-party/` and unzip them (so
+the extracted directories exist before the build runs) — the four from libetpan
+plus the `ctemplate-android-3.zip` produced in step 1b:
+
+```sh
+cd build-android
+mkdir -p third-party
+cp dependencies/ctemplate/ctemplate-android-3.zip third-party/
+cp /path/to/libetpan/build-android/{libetpan-android-7,\
+openssl-android-3,cyrus-sasl-android-4}.zip third-party/
+cp /path/to/libetpan/build-android/dependencies/iconv/iconv-android-1.zip third-party/
+cd third-party && for z in *.zip ; do unzip -oq "$z" ; done && cd ..
```
-export ANDROID_NDK=/Users/xxx/Library/Android/sdk/ndk/17.2.4988734
-export ANDROID_SDK=/Users/xxx/Library/Android/sdk
+
+`build.sh` skips any dependency whose directory already exists, so it will only
+download the three legacy C libs (`icu4c`, `libxml2`, `tidy`).
+
+## Step 3 — build the AAR
+
+```sh
+cd build-android
+./build.sh
```
-1. Compile openssl:
- - `cd build-android/dependencies/openssl`
- - build.sh: Change version in line 4 to most recent 1.0.2xxx, see https://ftp.openssl.org/source/old/1.0.2/. e.g. "1.0.2u"
- - `./build.sh` (Ignore that we get several errors regarding "cryptlib.h:62:21: fatal error: stdlib.h: No such file or directory" / "cp: libcrypto.a: No such file or directory" / "cp: libssl.a: No such file or directory". If the generated openssl-android-3.zip has e.g. 408KB and contains some h-files (but no so files), we are fine.)
-
-2. Compile iconv (probably not needed when removing "-DHAVE_ICONV=1" later)
- - `cd build-android/dependencies/iconv`
- - `./build.sh`
-
-3. Compile cyrus-sasl
- - cd build-mac/dependencies
- - prepare-cyrus-sasl.sh: Change version in line 5 to version=2.1.26 (to match version with build-android/dependencies/cyrus-sasl/build.sh)
- - `./prepare-cyrus-sasl.sh` (can be quitted after "building tools" is printed)
- - `cd build-android/dependencies/cyrus-sasl`
- - `./build.sh`
-
-4. Compile libetpan
- - `./autogen && make` to generate libetpan-config.h (Ignore that we error at make, we just need the libetpan-config.h file)
- - `cd build-android`
- - jni/Android.mk: Remove "-DHAVE_ICONV=1" in LOCAL_CFLAGS (line 131) (don't set it to 0 but remove it altogether) to build without iconv
- - `./build.sh`
-
-## Compile libmailcore
-- Download https://github.com/MailCore/mailcore2 (commit fad23d736ed5a63cf8321469d3a98a583f55df97 works definitely with this instruction): Make sure in this mailcore2 path are not spaces, this causes random compile errors
-- `cd build-android`
-- build.sh: Change libetpan version to 7
-- build.sh: Change `-source 1.6 -target 1.6` to `-source 1.8 -target 1.8`
-- `mkdir third-party`
-- Copy libetpan-android-7.zip (from step "Compile libetpan") to third-party folder and extract it
-- `./build.sh`
-
-## Shrink mailcore (by removing unnecessary files to reduce 50% of file size)
-- cd to the directory where `mailcore2-android-4.aar` is located
-- Execute
+Output: `build-android/mailcore2-android-4.aar`, containing
+`libMailCore.so` for **armeabi-v7a, arm64-v8a, x86**.
+
+Key build settings (`jni/Application.mk`):
+- `APP_STL := c++_static` — libc++ is linked into `libMailCore.so`; there is no
+ separate `libc++_shared.so`.
+- `APP_PLATFORM := android-23` — the rebuilt deps reference Bionic's API-23
+ symbols, so **minSdk 23 is the floor**.
+- `-Wl,-z,max-page-size=16384` — 16 KB-page alignment (Pixel 8/9 / Android 15+,
+ and a Google Play requirement from Nov 2025).
+
+## Using the AAR
+
+Drop it into an app module and depend on it directly:
+
+```kotlin
+// app/build.gradle.kts
+android { defaultConfig { minSdk = 23 } } // >= 23
+dependencies { implementation(files("libs/mailcore2-android-4.aar")) }
```
-unzip mailcore2-android-4.aar -d mailcore-unzipped
-rm mailcore2-android-4.aar
-cd mailcore-unzipped
-unzip classes.jar -d classes-unzipped
-rm -rf classes-unzipped/jni
-rm classes.jar
-jar cvf classes.jar -C classes-unzipped/ .
-rm -rf classes-unzipped
-cd ..
-jar cvf mailcore2-android-4.aar -C mailcore-unzipped/ .
-rm -rf mailcore-unzipped
+
+A complete, runnable Kotlin sample (IMAP connect / login / list folders +
+recent messages) is in [`../example/android/AndroidExample`](../example/android/AndroidExample).
+
+### Optional: shrink the AAR
+
+The `.aar` bundles the JNI `.so` per ABI. To strip the Java-side `jni/` copy and
+keep only what an app needs:
+
+```sh
+unzip mailcore2-android-4.aar -d unzipped && rm mailcore2-android-4.aar
+cd unzipped && unzip classes.jar -d classes && rm -rf classes/jni classes.jar
+jar cf classes.jar -C classes . && rm -rf classes && cd ..
+jar cf mailcore2-android-4.aar -C unzipped . && rm -rf unzipped
```
-- Copy shrinked `mailcore2-android-4.aar` to android project and test it on real devices
-
-## Troubleshooting
-- Openssl can't be downloaded: adapt letter in version based on latest release in https://ftp.openssl.org/source/ or https://ftp.openssl.org/source/old/1.0.2/
-- Compile openssl 1.0.2u with NDK 17 and libsasl 2.1.26
-- Compile openssl 1.1.1k with NDK 20 and libsasl 2.1.27
-- openssl compile error "aarch64-linux-android-gcc: No such file or directory": Make sure to use correct NDK
-- Android Looper/Handler or `android.os` package not found: Make sure to install all needed Android SDK versions in Android Studio
-- "Missing archive cyrus-sasl-2.1.26": Make sure to run `prepare-cyrus-sasl.sh` first
-- Build scripts don't always exit on errors: Manually search for "error:" or other errors
-- Missing gnustl_shared: Install & use Android NDK 17 (version 18 removed some GCC stuff)
-- https://stackoverflow.com/a/31534327/3997741 (but with "include" instead of "includes") (and in build-android in libetpan: `cp `find . -name '*.h'` ../../mailcore2-master/build-android/include/MailCore`
-- 'libetpan-config.h' file not found: Run `./autogen.sh && make` first
-- sasl/sasl.h not found: `cd build-mac/dependencies && ./prepare-cyrus-sasl.sh`
-- "Configuring OpenSSL version" missing: make sure to use current 1.1.1xxx/1.0.2xxx openssl version
-- "no sysroot" while building openssl: use Android NDK 20 instead of 17
-- error: variable has incomplete type 'HMAC_CTX' (aka 'struct hmac_ctx_st'): use sasl 2.1.27 instead of 2.1.26 in build-mac/dependencies/prepare-cyrus-sasl.sh and build-android/dependencies/cyrus-sasl/build.sh
-- Buildscripts can't run: Call them with `bash build.sh` (even when starting with "#! /bin/sh")
-- iconv stuff not found when linking: you missed the step to remove "-DHAVE_ICONV=1"
-
-### Running example ###
-
-Copy the binary result of the build (mailcore2-android-*version*.aar) to `mailcore2/example/android/AndroidExample/app/libs`.
-
-- Open the example in Android Studio
-- Tweaks the login and password in the class `MessagesSyncManager`
-- Then, run it.
+
+## Notes
+
+- **OpenSSL 1.1.1w is end-of-life (since Sept 2023).** Fine for internal use;
+ plan an OpenSSL 3.x dependency rebuild before depending on this long-term.
+- `IMAPSession.setConnectionLogger()` is broken in the Android binding (native
+ reads the `connectionLogger` field as a `long`, but it is an object) — avoid it.
diff --git a/build-android/build.sh b/build-android/build.sh
index 274bb6326..8788269ce 100755
--- a/build-android/build.sh
+++ b/build-android/build.sh
@@ -5,7 +5,8 @@ package_name=mailcore2-android
ctemplate_build_version=3
cyrus_sasl_build_version=4
icu4c_build_version=3
-libetpan_build_version=6
+libetpan_build_version=7
+iconv_build_version=1
libxml2_build_version=3
tidy_html5_build_version=3
openssl_build_version=3
@@ -26,24 +27,27 @@ function download_dep {
name="$1"
version="$2"
if test ! -f "$current_dir/third-party/$name-$version.zip" ; then
- if curl -f -O "http://d.etpan.org/mailcore2-deps/$name/$name-$version.zip" ; then
- unzip "$name-$version.zip"
- else
+ if ! curl -f -O "http://d.etpan.org/mailcore2-deps/$name/$name-$version.zip" ; then
echo Could not download $name-$version.zip
exit 1
fi
fi
+ # Always ensure the zip is extracted — covers zips dropped in manually
+ # (e.g. locally rebuilt deps) that were never unpacked.
+ if test ! -d "$current_dir/third-party/$name-$version" ; then
+ unzip -q "$current_dir/third-party/$name-$version.zip"
+ fi
}
function build {
rm -rf "$current_dir/obj"
cd "$current_dir/jni"
- $ANDROID_NDK/ndk-build TARGET_PLATFORM=$ANDROID_PLATFORM TARGET_ARCH_ABI=$TARGET_ARCH_ABI \
- NDK_TOOLCHAIN_VERSION=4.9 \
+ $ANDROID_NDK/ndk-build APP_ABI=$TARGET_ARCH_ABI APP_PLATFORM=$ANDROID_PLATFORM \
CTEMPLATE_PATH=$current_dir/third-party/ctemplate-android-$ctemplate_build_version \
ICU4C_PATH=$current_dir/third-party/icu4c-android-$icu4c_build_version \
LIBETPAN_PATH=$current_dir/third-party/libetpan-android-$libetpan_build_version \
+ LIBICONV_PATH=$current_dir/third-party/iconv-android-$iconv_build_version \
LIBXML2_PATH=$current_dir/third-party/libxml2-android-$libxml2_build_version \
TIDY_HTML5_PATH=$current_dir/third-party/tidy-html5-android-$tidy_html5_build_version \
OPENSSL_PATH=$current_dir/third-party/openssl-android-$openssl_build_version \
@@ -51,15 +55,15 @@ function build {
mkdir -p "$current_dir/bin/jni/$TARGET_ARCH_ABI"
cp "$current_dir/libs/$TARGET_ARCH_ABI/libMailCore.so" "$current_dir/bin/jni/$TARGET_ARCH_ABI"
- # cp "$ANDROID_NDK/sources/cxx-stl/llvm-libc++/libs/$TARGET_ARCH_ABI/libc++_shared.so" "$current_dir/bin/jni/$TARGET_ARCH_ABI"
- cp "$ANDROID_NDK/sources/cxx-stl/gnu-libstdc++/4.9/libs/$TARGET_ARCH_ABI/libgnustl_shared.so" "$current_dir/bin/jni/$TARGET_ARCH_ABI"
+ # With APP_STL := c++_static, libc++ is linked into libMailCore.so — there is no
+ # separate libc++_shared.so to bundle.
rm -rf "$current_dir/obj"
rm -rf "$current_dir/libs"
}
mkdir -p "$current_dir/cmake-build"
cd "$current_dir/cmake-build"
-cmake -D ANDROID=1 ../..
+cmake -D ANDROID=1 -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ../..
mkdir -p "$current_dir/include"
cp -R "$current_dir/cmake-build/src/include/MailCore" "$current_dir/include"
@@ -75,14 +79,12 @@ download_dep "openssl-android" $openssl_build_version
download_dep "cyrus-sasl-android" $cyrus_sasl_build_version
# Start building.
-ANDROID_PLATFORM=android-16
-archs="armeabi armeabi-v7a x86"
-for arch in $archs ; do
- TARGET_ARCH_ABI=$arch
- build
-done
-ANDROID_PLATFORM=android-21
-archs="arm64-v8a"
+# NDK r18+ dropped the armeabi (ARMv5) ABI. Use android-23 to match the prebuilt deps
+# (built at API 23 — they reference Bionic's API-23 stdin/stderr symbols).
+# x86_64 is omitted: the mailcore-only deps (icu4c/xml2/tidy/ctemplate) only ship
+# armeabi-v7a / arm64-v8a / x86.
+ANDROID_PLATFORM=android-23
+archs="armeabi-v7a arm64-v8a x86"
for arch in $archs ; do
TARGET_ARCH_ABI=$arch
build
@@ -91,12 +93,14 @@ done
ANDROID_PLATFORM=android-16
cd "$current_dir/../src/java"
mkdir -p "$current_dir/bin"
-javac -d "$current_dir/bin" -source 1.6 -target 1.6 -classpath $ANDROID_SDK/platforms/$ANDROID_PLATFORM/android.jar com/libmailcore/*.java
+javac -d "$current_dir/bin" -source 1.8 -target 1.8 -classpath $ANDROID_SDK/platforms/$ANDROID_PLATFORM/android.jar com/libmailcore/*.java
cd "$current_dir/bin"
jar cf classes.jar .
rm -rf "$current_dir/bin/com"
mkdir -p res
sed -e "s/android:versionCode=\"1\"/android:versionCode=\"$build_version\"/" "$current_dir/AndroidManifest.xml" > "$current_dir/bin/AndroidManifest.xml"
+# Remove any previous aar first — zip appends, which would leave stale .so from old builds.
+rm -f "$current_dir/$package_name-$build_version.aar"
zip -qry "$current_dir/$package_name-$build_version.aar" .
rm -rf "$current_dir/bin"
diff --git a/build-android/dependencies/ctemplate/build.sh b/build-android/dependencies/ctemplate/build.sh
new file mode 100755
index 000000000..1f75fe345
--- /dev/null
+++ b/build-android/dependencies/ctemplate/build.sh
@@ -0,0 +1,98 @@
+#!/bin/sh
+
+# ctemplate is a mailcore2 dependency (HTML rendering, src/core/renderer). It is
+# the one dependency that exposes C++ STL types (std::string) across its public
+# API, so it MUST be compiled with the same STL as mailcore2 — libc++. The
+# original d.etpan.org prebuilt was built against gnustl and fails to link
+# against an NDK r18+ / libc++ build. This script rebuilds it from source with
+# the NDK clang/libc++ toolchain.
+
+build_version=3
+package_name=ctemplate-android
+
+# dinhviethoa fork + revision used by mailcore2's own scripts/build-ctemplate-osx.sh
+ctemplate_url=https://github.com/dinhviethoa/ctemplate
+ctemplate_rev=b50f83f26ce3658889db1ae41b6a2d5874a3a10f
+
+export MIN_SDK_VERSION=23
+export HOST_TAG=darwin-x86_64
+
+if test "x$ANDROID_NDK" = x ; then
+ echo should set ANDROID_NDK before running this script.
+ exit 1
+fi
+
+current_dir="`pwd`"
+export TOOLCHAIN=$ANDROID_NDK/toolchains/llvm/prebuilt/$HOST_TAG
+export AR=$TOOLCHAIN/bin/llvm-ar
+export RANLIB=$TOOLCHAIN/bin/llvm-ranlib
+PATH=$TOOLCHAIN/bin:$PATH
+
+if test -f "$current_dir/$package_name-$build_version.zip" ; then
+ echo "$package_name-$build_version.zip already exists; remove it to rebuild."
+ exit 0
+fi
+
+rm -rf "$current_dir/src"
+mkdir -p "$current_dir/src"
+cd "$current_dir/src"
+
+# --- Fetch source -----------------------------------------------------------
+git clone "$ctemplate_url" ctemplate
+cd ctemplate
+git checkout "$ctemplate_rev"
+
+# config.sub/config.guess in this 2014-era fork predate the Android triples;
+# fetch current ones so ./configure accepts aarch64/armv7a/i686/x86_64-*-android.
+curl -L -o config.sub "https://git.savannah.gnu.org/cgit/config.git/plain/config.sub"
+curl -L -o config.guess "https://git.savannah.gnu.org/cgit/config.git/plain/config.guess"
+chmod +x config.sub config.guess
+
+# --- Generate htmlparser finite-state-machine headers -----------------------
+# generate_fsm.py (and fsm_config.py) are Python 2; convert them to Python 3,
+# then generate the *_fsm.h headers the parser sources #include.
+( cd src/htmlparser
+ 2to3 -w -n generate_fsm.py fsm_config.py >/dev/null 2>&1
+ python3 generate_fsm.py htmlparser_fsm.config > htmlparser_fsm.h
+ python3 generate_fsm.py jsparser_fsm.config > jsparser_fsm.h )
+
+CTEMPLATE_SRC="$current_dir/src/ctemplate"
+
+# abi : configure --host triple : NDK clang prefix
+build_abi() {
+ abi="$1" ; host="$2" ; cc="$3"
+ echo "############ ctemplate $abi ############"
+ cd "$CTEMPLATE_SRC"
+ make distclean >/dev/null 2>&1
+
+ export CC="$TOOLCHAIN/bin/${cc}${MIN_SDK_VERSION}-clang"
+ export CXX="$TOOLCHAIN/bin/${cc}${MIN_SDK_VERSION}-clang++"
+ export CFLAGS="-fPIC -O2"
+ export CXXFLAGS="-fPIC -O2 -std=c++11"
+
+ ./configure --host="$host" --enable-static --disable-shared \
+ --disable-dependency-tracking
+ # Build only the library (the test/utility binaries can't run when cross-built).
+ make -j5 libctemplate.la
+
+ out="$current_dir/$package_name-$build_version"
+ mkdir -p "$out/libs/$abi"
+ cp .libs/libctemplate.a "$out/libs/$abi/libctemplate.a"
+
+ # Public headers (incl. configure-generated ones) — copy once.
+ if test ! -d "$out/include/ctemplate" ; then
+ mkdir -p "$out/include/ctemplate"
+ cp src/ctemplate/*.h "$out/include/ctemplate/"
+ fi
+}
+
+build_abi arm64-v8a aarch64-linux-android aarch64-linux-android
+build_abi armeabi-v7a armv7a-linux-androideabi armv7a-linux-androideabi
+build_abi x86 i686-linux-android i686-linux-android
+build_abi x86_64 x86_64-linux-android x86_64-linux-android
+
+cd "$current_dir"
+zip -qry "$package_name-$build_version.zip" "$package_name-$build_version"
+rm -rf "$current_dir/$package_name-$build_version"
+rm -rf "$current_dir/src"
+echo "Built $package_name-$build_version.zip"
diff --git a/build-android/jni/Android.mk b/build-android/jni/Android.mk
index 9b47cf0ee..ba3b3755d 100644
--- a/build-android/jni/Android.mk
+++ b/build-android/jni/Android.mk
@@ -28,11 +28,8 @@ includes = \
$(LIBXML2_PATH)/include \
$(TIDY_HTML5_PATH)/include \
$(OPENSSL_PATH)/include \
- $(ANDROID_NDK)/sources/cxx-stl/gnu-libstdc++/4.9/include \
- $(ANDROID_NDK)/sources/cxx-stl/gnu-libstdc++/4.9/libs/$(TARGET_ARCH_ABI)/include \
$(addprefix $(src_dir)/, $(subdirs))
-# $(ANDROID_NDK)/sources/cxx-stl/llvm-libc++/libcxx/include
-# $(ANDROID_NDK)/sources/cxx-stl/llvm-libc++abi/libcxxabi/include
+# libc++ headers are added automatically by ndk-build via APP_STL := c++_shared (see Application.mk)
core_excludes = MCWin32.cpp MCStringWin32.cpp MCMainThreadWin32.cpp MCMainThreadGTK.cpp
core_src_files := $(filter-out \
@@ -80,6 +77,11 @@ LOCAL_MODULE := crypto
LOCAL_SRC_FILES := $(OPENSSL_PATH)/libs/$(TARGET_ARCH_ABI)/libcrypto.a
include $(PREBUILT_STATIC_LIBRARY)
+include $(CLEAR_VARS)
+LOCAL_MODULE := iconv
+LOCAL_SRC_FILES := $(LIBICONV_PATH)/libs/$(TARGET_ARCH_ABI)/libiconv.a
+include $(PREBUILT_STATIC_LIBRARY)
+
include $(CLEAR_VARS)
LOCAL_MODULE := ssl
LOCAL_SRC_FILES := $(OPENSSL_PATH)/libs/$(TARGET_ARCH_ABI)/libssl.a
@@ -120,11 +122,12 @@ LOCAL_SRC_FILES := \
$(security_src_files) $(smtp_src_files) $(zip_src_files) $(minizip_src_files) \
$(async_imap_src_files) $(async_nntp_src_files) $(async_pop_src_files) $(async_smtp_src_files)
LOCAL_CPPFLAGS := -frtti
-LOCAL_CFLAGS := -DNOCRYPT
-# LOCAL_LDLIBS := -lz -llog \
-# -lc++_shared -L$(ANDROID_NDK)/sources/cxx-stl/llvm-libc++/libs/$(TARGET_ARCH_ABI)
-LOCAL_LDLIBS := -lz -llog \
- -lgnustl_shared -L$(ANDROID_NDK)/sources/cxx-stl/gnu-libstdc++/4.9/libs/$(TARGET_ARCH_ABI)
+# USE_FILE32API: MiniZip ioapi.c calls ftello64/fseeko64, which Bionic only declares at
+# API >= 24; this maps them to 32-bit ftell/fseek so the build works at android-21.
+LOCAL_CFLAGS := -DNOCRYPT -DUSE_FILE32API
+# With APP_STL := c++_shared, ndk-build links libc++_shared automatically; just link system libs.
+LOCAL_LDLIBS := -lz -llog
LOCAL_DISABLE_FATAL_LINKER_WARNINGS := true
-LOCAL_STATIC_LIBRARIES := etpan sasl2 ssl crypto icu4c xml2 tidy ctemplate
+# Order matters (ndk-build links left-to-right): etpan needs ssl/crypto/iconv.
+LOCAL_STATIC_LIBRARIES := etpan sasl2 ssl crypto iconv icu4c xml2 tidy ctemplate
include $(BUILD_SHARED_LIBRARY)
diff --git a/build-android/jni/Application.mk b/build-android/jni/Application.mk
new file mode 100644
index 000000000..16ff6f13c
--- /dev/null
+++ b/build-android/jni/Application.mk
@@ -0,0 +1,13 @@
+# Modernized: use the LLVM libc++ STL (gnustl / GCC were removed in NDK r18).
+# c++_static: libMailCore.so is the only native lib in the .aar and exposes a JNI/Java
+# boundary (no C++ types cross to other libs), so static libc++ is safe and avoids
+# shipping a separate libc++_shared.so — which matters for 16KB alignment (below).
+APP_STL := c++_static
+APP_CPPFLAGS := -frtti
+# android-23: the prebuilt deps (openssl/iconv/cyrus-sasl) are built at API 23, where
+# Bionic exports stdin/stderr/stdout as real symbols. Linking at a lower API fails with
+# "undefined symbol: stdin/stderr".
+APP_PLATFORM := android-23
+# 16KB page alignment: required to load on 16KB-page devices (Pixel 8/9, Android 15+)
+# and by Google Play for new/updated apps from Nov 2025. NDK 26.3 defaults to 4KB.
+APP_LDFLAGS := -Wl,-z,max-page-size=16384
diff --git a/example/android/AndroidExample/.gitignore b/example/android/AndroidExample/.gitignore
index afbdab33e..30ea4c5ba 100644
--- a/example/android/AndroidExample/.gitignore
+++ b/example/android/AndroidExample/.gitignore
@@ -1,6 +1,7 @@
+*.iml
.gradle
/local.properties
-/.idea/workspace.xml
-/.idea/libraries
-.DS_Store
+/.idea
/build
+/app/build
+/captures
diff --git a/example/android/AndroidExample/AndroidExample.iml b/example/android/AndroidExample/AndroidExample.iml
deleted file mode 100644
index 2a0220140..000000000
--- a/example/android/AndroidExample/AndroidExample.iml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/example/android/AndroidExample/README.md b/example/android/AndroidExample/README.md
new file mode 100644
index 000000000..8d074614c
--- /dev/null
+++ b/example/android/AndroidExample/README.md
@@ -0,0 +1,44 @@
+# MailCore2 Android example (Kotlin)
+
+A minimal modern Kotlin app that integrates **mailcore2** via the prebuilt
+`mailcore2-android-4.aar` and demonstrates a real IMAP sync: connect over TLS,
+list folders, and fetch the headers of the most recent INBOX messages.
+
+## Layout
+- `app/libs/mailcore2-android-4.aar` — the mailcore2 library (built from `build-android/`).
+- `app/src/main/java/com/libmailcore/androidexample/MainActivity.kt` — all the mailcore usage.
+
+## Toolchain
+- Gradle 8.7, Android Gradle Plugin 8.5.2, Kotlin 1.9.24, JDK 17.
+- `compileSdk 34`, `targetSdk 34`, **`minSdk 23`** (the .aar's native deps are built at API 23).
+- ABIs: `armeabi-v7a`, `arm64-v8a`, `x86` (the .aar ships these).
+
+## Build & run
+```sh
+# point Gradle at your SDK (or rely on $ANDROID_HOME)
+echo "sdk.dir=$HOME/Library/Android/sdk" > local.properties
+
+./gradlew :app:assembleDebug
+./gradlew :app:installDebug # with a device/emulator attached
+```
+Then open the app, edit the host/username/password fields, and tap **Connect & sync**.
+The credentials are pre-filled for convenience — replace them with your own.
+
+For headless/automated runs the sync can be triggered without a tap:
+```sh
+adb shell am start -n com.libmailcore.androidexample/.MainActivity --ez autostart true
+adb logcat -s MailCoreExample # results are mirrored to logcat
+```
+
+## Integrating mailcore2 in your own project
+1. Drop `mailcore2-android-4.aar` into your module's `libs/` and add:
+ ```kotlin
+ implementation(files("libs/mailcore2-android-4.aar"))
+ ```
+2. Use `minSdk 23` or higher.
+3. Drive IMAP through `com.libmailcore.IMAPSession` and its `*Operation` objects,
+ each started with an `OperationCallback` (`succeeded()` / `failed()`); callbacks
+ are delivered on the main thread. See `MainActivity.kt`.
+
+Note: `IMAPSession.setConnectionLogger(...)` is currently broken in the Android
+binding (native/Java field-type mismatch) and will abort — avoid it.
diff --git a/example/android/AndroidExample/app/.gitignore b/example/android/AndroidExample/app/.gitignore
deleted file mode 100644
index 796b96d1c..000000000
--- a/example/android/AndroidExample/app/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/example/android/AndroidExample/app/app.iml b/example/android/AndroidExample/app/app.iml
deleted file mode 100644
index 6720fe38a..000000000
--- a/example/android/AndroidExample/app/app.iml
+++ /dev/null
@@ -1,92 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/example/android/AndroidExample/app/build.gradle b/example/android/AndroidExample/app/build.gradle
deleted file mode 100644
index 54a946efb..000000000
--- a/example/android/AndroidExample/app/build.gradle
+++ /dev/null
@@ -1,35 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 21
- buildToolsVersion "21.1.1"
-
- defaultConfig {
- applicationId "com.libmailcore.androidexample"
- minSdkVersion 21
- targetSdkVersion 21
- versionCode 1
- versionName "1.0"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
-}
-
-repositories {
- flatDir {
- dirs 'libs'
- }
-}
-
-dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- compile fileTree(dir: 'libs', include: ['*.jar'])
- compile(name: 'mailcore2-android-1', ext: 'aar')
- compile 'com.android.support:support-v4:21.0.2'
- compile 'com.google.android.gms:play-services:6.5.87'
- compile 'com.android.support:appcompat-v7:21.0.2'
-}
diff --git a/example/android/AndroidExample/app/build.gradle.kts b/example/android/AndroidExample/app/build.gradle.kts
new file mode 100644
index 000000000..f1b0df3bd
--- /dev/null
+++ b/example/android/AndroidExample/app/build.gradle.kts
@@ -0,0 +1,42 @@
+plugins {
+ id("com.android.application")
+ id("org.jetbrains.kotlin.android")
+}
+
+android {
+ namespace = "com.libmailcore.androidexample"
+ compileSdk = 34
+
+ defaultConfig {
+ applicationId = "com.libmailcore.androidexample"
+ // mailcore2-android-4.aar links its native deps at API 23, so minSdk must be >= 23.
+ minSdk = 23
+ targetSdk = 34
+ versionCode = 1
+ versionName = "1.0"
+ // The .aar ships these ABIs only.
+ ndk { abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86") }
+ }
+
+ buildFeatures { viewBinding = true }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ kotlinOptions { jvmTarget = "17" }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ }
+ }
+}
+
+dependencies {
+ // mailcore2 as a local .aar (native libMailCore.so + com.libmailcore Java API)
+ implementation(files("libs/mailcore2-android-4.aar"))
+
+ implementation("androidx.core:core-ktx:1.13.1")
+ implementation("androidx.appcompat:appcompat:1.6.1")
+}
diff --git a/example/android/AndroidExample/app/libs/mailcore2-android-4.aar b/example/android/AndroidExample/app/libs/mailcore2-android-4.aar
new file mode 100644
index 000000000..0c9f5184a
Binary files /dev/null and b/example/android/AndroidExample/app/libs/mailcore2-android-4.aar differ
diff --git a/example/android/AndroidExample/app/proguard-rules.pro b/example/android/AndroidExample/app/proguard-rules.pro
deleted file mode 100644
index 3c2c65b2f..000000000
--- a/example/android/AndroidExample/app/proguard-rules.pro
+++ /dev/null
@@ -1,17 +0,0 @@
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in /Users/hoa/Android/android-sdk-macosx/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
diff --git a/example/android/AndroidExample/app/src/main/AndroidManifest.xml b/example/android/AndroidExample/app/src/main/AndroidManifest.xml
index 58481ab88..3e262abea 100644
--- a/example/android/AndroidExample/app/src/main/AndroidManifest.xml
+++ b/example/android/AndroidExample/app/src/main/AndroidManifest.xml
@@ -1,49 +1,21 @@
-
+
-
-
-
-
-
-
+ android:label="MailCore2 Example"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.AppCompat.Light.DarkActionBar">
+ android:name=".MainActivity"
+ android:exported="true">
-
-
-
-
-
-
-
-
diff --git a/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MainActivity.kt b/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MainActivity.kt
new file mode 100644
index 000000000..bafe16686
--- /dev/null
+++ b/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MainActivity.kt
@@ -0,0 +1,114 @@
+package com.libmailcore.androidexample
+
+import android.os.Bundle
+import android.util.Log
+import androidx.appcompat.app.AppCompatActivity
+import com.libmailcore.androidexample.databinding.ActivityMainBinding
+import com.libmailcore.ConnectionType
+import com.libmailcore.IMAPSession
+import com.libmailcore.IMAPMessagesRequestKind
+import com.libmailcore.IndexSet
+import com.libmailcore.MailException
+import com.libmailcore.OperationCallback
+import com.libmailcore.Range
+
+/**
+ * Minimal mailcore2 sample: connect to an IMAP account over TLS, list the folders,
+ * then fetch the headers of the most recent messages in INBOX — all through the
+ * com.libmailcore API provided by mailcore2-android-4.aar.
+ *
+ * mailcore runs each operation on a background queue and delivers the
+ * OperationCallback on the main (UI) thread, so the UI updates below are safe.
+ */
+class MainActivity : AppCompatActivity() {
+
+ private lateinit var binding: ActivityMainBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityMainBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ binding.connectButton.setOnClickListener { startSync() }
+
+ // Allows headless launch: `am start -n .../.MainActivity --ez autostart true`
+ if (intent?.getBooleanExtra("autostart", false) == true) startSync()
+ }
+
+ private fun log(line: String) = runOnUiThread {
+ binding.outputView.append(line + "\n")
+ Log.i(TAG, line)
+ }
+
+ private fun startSync() {
+ binding.outputView.text = ""
+ binding.connectButton.isEnabled = false
+
+ val session = IMAPSession()
+ session.setHostname(binding.hostField.text.toString().trim())
+ session.setPort(binding.portField.text.toString().trim().toIntOrNull() ?: 993)
+ session.setUsername(binding.userField.text.toString().trim())
+ session.setPassword(binding.passField.text.toString())
+ session.setConnectionType(ConnectionType.ConnectionTypeTLS)
+
+ log("Connecting to ${binding.hostField.text}…")
+
+ val foldersOp = session.fetchAllFoldersOperation()
+ foldersOp.start(object : OperationCallback {
+ override fun succeeded() {
+ val folders = foldersOp.folders()
+ log("✓ Connected. Folders (${folders.size}):")
+ folders.forEach { log(" • ${it.path()}") }
+ fetchInbox(session)
+ }
+ override fun failed(e: MailException) = finishWith("✗ Folder list failed: ${e.message}")
+ })
+ }
+
+ private fun fetchInbox(session: IMAPSession) {
+ val infoOp = session.folderInfoOperation("INBOX")
+ infoOp.start(object : OperationCallback {
+ override fun succeeded() {
+ val count = infoOp.info().messageCount()
+ log("\nINBOX: $count messages")
+ if (count <= 0) { finishWith("\n✓ Done"); return }
+
+ val from = maxOf(1, count - 9)
+ // Range spans location..location+length (inclusive), so length is the delta,
+ // not the element count: a single message is Range(n, 0).
+ val length = (count - from).toLong()
+ val kind = IMAPMessagesRequestKind.IMAPMessagesRequestKindHeaders or
+ IMAPMessagesRequestKind.IMAPMessagesRequestKindFlags or
+ IMAPMessagesRequestKind.IMAPMessagesRequestKindUid
+ val indexes = IndexSet.indexSetWithRange(Range(from.toLong(), length))
+
+ val fetchOp = session.fetchMessagesByNumberOperation("INBOX", kind, indexes)
+ fetchOp.start(object : OperationCallback {
+ override fun succeeded() {
+ val messages = fetchOp.messages()
+ log("\nLast ${messages.size} messages:")
+ messages.reversed().forEach { m ->
+ val header = m.header()
+ val subject = header?.subject() ?: "(no subject)"
+ val sender = header?.from()?.let { it.displayName() ?: it.mailbox() } ?: "?"
+ log(" [${m.uid()}] $sender — $subject")
+ }
+ finishWith("\n✓ Done")
+ }
+ override fun failed(e: MailException) =
+ finishWith("✗ Message fetch failed (code=${e.errorCode()}): ${e.message}")
+ })
+ }
+ override fun failed(e: MailException) = finishWith("✗ INBOX info failed: ${e.message}")
+ })
+ }
+
+ private fun finishWith(message: String) {
+ log(message)
+ runOnUiThread { binding.connectButton.isEnabled = true }
+ }
+
+ companion object {
+ private const val TAG = "MailCoreExample"
+ }
+}
diff --git a/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessageAdapter.java b/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessageAdapter.java
deleted file mode 100644
index 86781ce66..000000000
--- a/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessageAdapter.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.libmailcore.androidexample;
-
-import com.libmailcore.IMAPMessage;
-
-/**
- * Created by hoa on 1/9/15.
- */
-public class MessageAdapter {
- IMAPMessage message;
-
- public MessageAdapter(IMAPMessage msg) {
- message = msg;
- }
-
- public String toString() {
- return message.header().from().displayName() + " " + message.header().extractedSubject();
- }
-}
diff --git a/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessageViewDetailActivity.java b/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessageViewDetailActivity.java
deleted file mode 100644
index e12f71b11..000000000
--- a/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessageViewDetailActivity.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.libmailcore.androidexample;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.app.Activity;
-
-import android.view.MenuItem;
-
-
-/**
- * An activity representing a single MessageView detail screen. This
- * activity is only used on handset devices. On tablet-size devices,
- * item details are presented side-by-side with a list of items
- * in a {@link MessageViewListActivity}.
- *
- * This activity is mostly just a 'shell' activity containing nothing
- * more than a {@link MessageViewDetailFragment}.
- */
-public class MessageViewDetailActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_messageview_detail);
-
- // Show the Up button in the action bar.
- getActionBar().setDisplayHomeAsUpEnabled(true);
-
- // savedInstanceState is non-null when there is fragment state
- // saved from previous configurations of this activity
- // (e.g. when rotating the screen from portrait to landscape).
- // In this case, the fragment will automatically be re-added
- // to its container so we don't need to manually add it.
- // For more information, see the Fragments API guide at:
- //
- // http://developer.android.com/guide/components/fragments.html
- //
- if (savedInstanceState == null) {
- // Create the detail fragment and add it to the activity
- // using a fragment transaction.
- /*
- Bundle arguments = new Bundle();
- arguments.putString(MessageViewDetailFragment.ARG_ITEM_ID,
- getIntent().getStringExtra(MessageViewDetailFragment.ARG_ITEM_ID));
- */
-
- MessageViewDetailFragment fragment = new MessageViewDetailFragment();
- //fragment.setArguments(arguments);
- getFragmentManager().beginTransaction()
- .add(R.id.messageview_detail_container, fragment)
- .commit();
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- int id = item.getItemId();
- if (id == android.R.id.home) {
- // This ID represents the Home or Up button. In the case of this
- // activity, the Up button is shown. For
- // more details, see the Navigation pattern on Android Design:
- //
- // http://developer.android.com/design/patterns/navigation.html#up-vs-back
- //
- navigateUpTo(new Intent(this, MessageViewListActivity.class));
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-}
diff --git a/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessageViewDetailFragment.java b/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessageViewDetailFragment.java
deleted file mode 100644
index 4fd802820..000000000
--- a/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessageViewDetailFragment.java
+++ /dev/null
@@ -1,84 +0,0 @@
-package com.libmailcore.androidexample;
-
-import android.os.Bundle;
-import android.app.Fragment;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.webkit.WebView;
-import android.util.Log;
-
-
-import com.libmailcore.MailException;
-import com.libmailcore.OperationCallback;
-import com.libmailcore.IMAPMessage;
-import com.libmailcore.IMAPMessageRenderingOperation;
-
-/**
- * A fragment representing a single MessageView detail screen.
- * This fragment is either contained in a {@link MessageViewListActivity}
- * in two-pane mode (on tablets) or a {@link MessageViewDetailActivity}
- * on handsets.
- */
-public class MessageViewDetailFragment extends Fragment implements OperationCallback {
- /**
- * The fragment argument representing the item ID that this fragment
- * represents.
- */
- public static final String ARG_ITEM_ID = "item_id";
-
- /**
- * The dummy content this fragment is presenting.
- */
- //private DummyContent.DummyItem mItem;
- private IMAPMessage message;
-
- /**
- * Mandatory empty constructor for the fragment manager to instantiate the
- * fragment (e.g. upon screen orientation changes).
- */
- public MessageViewDetailFragment() {
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- /*
- if (getArguments().containsKey(ARG_ITEM_ID)) {
- // Load the dummy content specified by the fragment
- // arguments. In a real-world scenario, use a Loader
- // to load content from a content provider.
- mItem = DummyContent.ITEM_MAP.get(getArguments().getString(ARG_ITEM_ID));
- }
- */
- message = MessagesSyncManager.singleton().currentMessage;
- }
-
- private IMAPMessageRenderingOperation renderingOp;
- private WebView webView;
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View rootView = inflater.inflate(R.layout.fragment_messageview_detail, container, false);
- webView = (WebView) rootView.findViewById(R.id.messageview_detail);
-
- if (message != null) {
- Log.d("detail", "message: " + message);
- renderingOp = MessagesSyncManager.singleton().session.htmlRenderingOperation(message, "INBOX");
- renderingOp.start(this);
- }
-
- return rootView;
- }
-
- public void succeeded() {
- String html = renderingOp.result();
- webView.loadData(html, "text/html", "utf-8");
- }
-
- public void failed(MailException exception) {
-
- }
-}
diff --git a/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessageViewListActivity.java b/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessageViewListActivity.java
deleted file mode 100644
index 4577b594d..000000000
--- a/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessageViewListActivity.java
+++ /dev/null
@@ -1,85 +0,0 @@
-package com.libmailcore.androidexample;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.app.Activity;
-import com.libmailcore.IMAPSession;
-import com.libmailcore.MailException;
-import com.libmailcore.OperationCallback;
-import com.libmailcore.IMAPMessage;
-
-/**
- * An activity representing a list of MessagesViews. This activity
- * has different presentations for handset and tablet-size devices. On
- * handsets, the activity presents a list of items, which when touched,
- * lead to a {@link MessageViewDetailActivity} representing
- * item details. On tablets, the activity presents the list of items and
- * item details side-by-side using two vertical panes.
- *
- * The activity makes heavy use of fragments. The list of items is a
- * {@link MessageViewListFragment} and the item details
- * (if present) is a {@link MessageViewDetailFragment}.
- *
- * This activity also implements the required
- * {@link MessageViewListFragment.Callbacks} interface
- * to listen for item selections.
- */
-public class MessageViewListActivity extends Activity
- implements MessageViewListFragment.Callbacks {
-
- /**
- * Whether or not the activity is in two-pane mode, i.e. running on a tablet
- * device.
- */
- private boolean mTwoPane;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_messageview_list);
-
- if (findViewById(R.id.messageview_detail_container) != null) {
- // The detail container view will be present only in the
- // large-screen layouts (res/values-large and
- // res/values-sw600dp). If this view is present, then the
- // activity should be in two-pane mode.
- mTwoPane = true;
-
- // In two-pane mode, list items should be given the
- // 'activated' state when touched.
- ((MessageViewListFragment) getFragmentManager()
- .findFragmentById(R.id.messageview_list))
- .setActivateOnItemClick(true);
- }
- }
-
- /**
- * Callback method from {@link MessageViewListFragment.Callbacks}
- * indicating that the item with the given ID was selected.
- */
- @Override
- public void onItemSelected(IMAPMessage msg) {
- if (mTwoPane) {
- // In two-pane mode, show the detail view in this activity by
- // adding or replacing the detail fragment using a
- // fragment transaction.
- MessagesSyncManager.singleton().currentMessage = msg;
-
- //Bundle arguments = new Bundle();
- //arguments.putString(MessageViewDetailFragment.ARG_ITEM_ID, msg);
- MessageViewDetailFragment fragment = new MessageViewDetailFragment();
- //fragment.setArguments(arguments);
- getFragmentManager().beginTransaction()
- .replace(R.id.messageview_detail_container, fragment)
- .commit();
-
- } else {
- // In single-pane mode, simply start the detail activity
- // for the selected item ID.
- MessagesSyncManager.singleton().currentMessage = msg;
- Intent detailIntent = new Intent(this, MessageViewDetailActivity.class);
- //detailIntent.putExtra(MessageViewDetailFragment.ARG_ITEM_ID, id);
- startActivity(detailIntent);
- }
- }
-}
diff --git a/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessageViewListFragment.java b/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessageViewListFragment.java
deleted file mode 100644
index f0ef51746..000000000
--- a/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessageViewListFragment.java
+++ /dev/null
@@ -1,189 +0,0 @@
-package com.libmailcore.androidexample;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.app.ListFragment;
-import android.view.View;
-import android.widget.ArrayAdapter;
-import android.widget.ListView;
-import android.util.Log;
-
-import java.util.ArrayList;
-
-import com.libmailcore.IMAPFetchMessagesOperation;
-import com.libmailcore.IMAPMessage;
-import com.libmailcore.MailException;
-import com.libmailcore.OperationCallback;
-import com.libmailcore.IndexSet;
-import com.libmailcore.IMAPMessagesRequestKind;
-import com.libmailcore.Range;
-
-/**
- * A list fragment representing a list of MessagesViews. This fragment
- * also supports tablet devices by allowing list items to be given an
- * 'activated' state upon selection. This helps indicate which item is
- * currently being viewed in a {@link MessageViewDetailFragment}.
- *
- * Activities containing this fragment MUST implement the {@link Callbacks}
- * interface.
- */
-public class MessageViewListFragment extends ListFragment implements OperationCallback {
-
- /**
- * The serialization (saved instance state) Bundle key representing the
- * activated item position. Only used on tablets.
- */
- private static final String STATE_ACTIVATED_POSITION = "activated_position";
-
- /**
- * The fragment's current callback object, which is notified of list item
- * clicks.
- */
- private Callbacks mCallbacks = sDummyCallbacks;
-
- /**
- * The current activated item position. Only used on tablets.
- */
- private int mActivatedPosition = ListView.INVALID_POSITION;
-
- /**
- * A callback interface that all activities containing this fragment must
- * implement. This mechanism allows activities to be notified of item
- * selections.
- */
- public interface Callbacks {
- /**
- * Callback for when an item has been selected.
- */
- public void onItemSelected(IMAPMessage msg);
- }
-
- /**
- * A dummy implementation of the {@link Callbacks} interface that does
- * nothing. Used only when this fragment is not attached to an activity.
- */
- private static Callbacks sDummyCallbacks = new Callbacks() {
- @Override
- public void onItemSelected(IMAPMessage msg) {
- }
- };
-
- /**
- * Mandatory empty constructor for the fragment manager to instantiate the
- * fragment (e.g. upon screen orientation changes).
- */
- public MessageViewListFragment() {
- }
-
- private IMAPFetchMessagesOperation fetchMessagesOp;
- private ArrayAdapter adapter;
- private java.util.List messages;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- fetchMessagesOp = MessagesSyncManager.singleton().session.fetchMessagesByNumberOperation("INBOX", IMAPMessagesRequestKind.IMAPMessagesRequestKindHeaders | IMAPMessagesRequestKind.IMAPMessagesRequestKindStructure, IndexSet.indexSetWithRange(new Range(1, Range.RangeMax)));
- fetchMessagesOp.start(this);
- }
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
-
- // Restore the previously serialized activated item position.
- if (savedInstanceState != null
- && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
- setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
- }
- }
-
- @Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
-
- // Activities containing this fragment must implement its callbacks.
- if (!(activity instanceof Callbacks)) {
- throw new IllegalStateException("Activity must implement fragment's callbacks.");
- }
-
- mCallbacks = (Callbacks) activity;
- }
-
- @Override
- public void onDetach() {
- super.onDetach();
-
- // Reset the active callbacks interface to the dummy implementation.
- mCallbacks = sDummyCallbacks;
- }
-
- @Override
- public void onListItemClick(ListView listView, View view, int position, long id) {
- super.onListItemClick(listView, view, position, id);
-
- // Notify the active callbacks interface (the activity, if the
- // fragment is attached to one) that an item has been selected.
-// mCallbacks.onItemSelected(DummyContent.ITEMS.get(position).id);
- Log.d("msglist", "row clicked: " + id);
- mCallbacks.onItemSelected(messages.get((int) id));
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- if (mActivatedPosition != ListView.INVALID_POSITION) {
- // Serialize and persist the activated item position.
- outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
- }
- }
-
- /**
- * Turns on activate-on-click mode. When this mode is on, list items will be
- * given the 'activated' state when touched.
- */
- public void setActivateOnItemClick(boolean activateOnItemClick) {
- // When setting CHOICE_MODE_SINGLE, ListView will automatically
- // give items the 'activated' state when touched.
- getListView().setChoiceMode(activateOnItemClick
- ? ListView.CHOICE_MODE_SINGLE
- : ListView.CHOICE_MODE_NONE);
- }
-
- private void setActivatedPosition(int position) {
- if (position == ListView.INVALID_POSITION) {
- getListView().setItemChecked(mActivatedPosition, false);
- } else {
- getListView().setItemChecked(position, true);
- }
-
- mActivatedPosition = position;
- }
-
- public void succeeded() {
- messages = fetchMessagesOp.messages();
- updateResult();
- }
-
- public void failed(MailException exception) {
-
- }
-
- private void updateResult() {
-
- Log.d("msglist", "update result");
- ArrayList array = new ArrayList();
- for(IMAPMessage msg : messages) {
- MessageAdapter msgAdapter = new MessageAdapter(msg);
- array.add(msgAdapter);
- }
- adapter = new ArrayAdapter(
- getActivity(),
- android.R.layout.simple_list_item_activated_1,
- android.R.id.text1,
- array);
-
- setListAdapter(adapter);
-
- }
-}
diff --git a/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessagesSyncManager.java b/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessagesSyncManager.java
deleted file mode 100644
index 3c25e2bfb..000000000
--- a/example/android/AndroidExample/app/src/main/java/com/libmailcore/androidexample/MessagesSyncManager.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.libmailcore.androidexample;
-
-import com.libmailcore.ConnectionType;
-import com.libmailcore.IMAPFetchMessagesOperation;
-import com.libmailcore.IMAPSession;
-import com.libmailcore.IMAPMessage;
-
-/**
- * Created by hoa on 1/8/15.
- */
-public class MessagesSyncManager {
- public IMAPSession session;
- public IMAPMessage currentMessage;
-
- static private MessagesSyncManager theSingleton;
-
- public static MessagesSyncManager singleton() {
- if (theSingleton == null) {
- theSingleton = new MessagesSyncManager();
- }
- return theSingleton;
- }
-
- private MessagesSyncManager() {
- session = new IMAPSession();
- session.setUsername("login@gmail.com");
- session.setPassword("password");
- session.setHostname("imap.gmail.com");
- session.setPort(993);
- session.setConnectionType(ConnectionType.ConnectionTypeTLS);
- }
-
-
-}
diff --git a/example/android/AndroidExample/app/src/main/res/drawable-hdpi/ic_launcher.png b/example/android/AndroidExample/app/src/main/res/drawable-hdpi/ic_launcher.png
deleted file mode 100644
index 96a442e5b..000000000
Binary files a/example/android/AndroidExample/app/src/main/res/drawable-hdpi/ic_launcher.png and /dev/null differ
diff --git a/example/android/AndroidExample/app/src/main/res/drawable-mdpi/ic_launcher.png b/example/android/AndroidExample/app/src/main/res/drawable-mdpi/ic_launcher.png
deleted file mode 100644
index 359047dfa..000000000
Binary files a/example/android/AndroidExample/app/src/main/res/drawable-mdpi/ic_launcher.png and /dev/null differ
diff --git a/example/android/AndroidExample/app/src/main/res/drawable-xhdpi/ic_launcher.png b/example/android/AndroidExample/app/src/main/res/drawable-xhdpi/ic_launcher.png
deleted file mode 100644
index 71c6d760f..000000000
Binary files a/example/android/AndroidExample/app/src/main/res/drawable-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/example/android/AndroidExample/app/src/main/res/drawable-xxhdpi/ic_launcher.png b/example/android/AndroidExample/app/src/main/res/drawable-xxhdpi/ic_launcher.png
deleted file mode 100644
index 4df189464..000000000
Binary files a/example/android/AndroidExample/app/src/main/res/drawable-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/example/android/AndroidExample/app/src/main/res/layout-sw600dp/activity_messageview_list.xml b/example/android/AndroidExample/app/src/main/res/layout-sw600dp/activity_messageview_list.xml
deleted file mode 100644
index 9b211bdab..000000000
--- a/example/android/AndroidExample/app/src/main/res/layout-sw600dp/activity_messageview_list.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/example/android/AndroidExample/app/src/main/res/layout/activity_main.xml b/example/android/AndroidExample/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 000000000..c8cd90a7f
--- /dev/null
+++ b/example/android/AndroidExample/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/android/AndroidExample/app/src/main/res/layout/activity_messageview_detail.xml b/example/android/AndroidExample/app/src/main/res/layout/activity_messageview_detail.xml
deleted file mode 100644
index fa100d0bb..000000000
--- a/example/android/AndroidExample/app/src/main/res/layout/activity_messageview_detail.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/example/android/AndroidExample/app/src/main/res/layout/activity_messageview_list.xml b/example/android/AndroidExample/app/src/main/res/layout/activity_messageview_list.xml
deleted file mode 100644
index 039f69b1d..000000000
--- a/example/android/AndroidExample/app/src/main/res/layout/activity_messageview_list.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
diff --git a/example/android/AndroidExample/app/src/main/res/layout/fragment_messageview_detail.xml b/example/android/AndroidExample/app/src/main/res/layout/fragment_messageview_detail.xml
deleted file mode 100644
index d1ecbf756..000000000
--- a/example/android/AndroidExample/app/src/main/res/layout/fragment_messageview_detail.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/example/android/AndroidExample/app/src/main/res/values-v21/styles.xml b/example/android/AndroidExample/app/src/main/res/values-v21/styles.xml
deleted file mode 100644
index dba3c417b..000000000
--- a/example/android/AndroidExample/app/src/main/res/values-v21/styles.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
diff --git a/example/android/AndroidExample/app/src/main/res/values/dimens.xml b/example/android/AndroidExample/app/src/main/res/values/dimens.xml
deleted file mode 100644
index 47c822467..000000000
--- a/example/android/AndroidExample/app/src/main/res/values/dimens.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
- 16dp
- 16dp
-
diff --git a/example/android/AndroidExample/app/src/main/res/values/strings.xml b/example/android/AndroidExample/app/src/main/res/values/strings.xml
deleted file mode 100644
index 986a9ee4f..000000000
--- a/example/android/AndroidExample/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- AndroidExample
- MessagesViews
- MessageView Detail
-
-
diff --git a/example/android/AndroidExample/app/src/main/res/values/strings_activity_login.xml b/example/android/AndroidExample/app/src/main/res/values/strings_activity_login.xml
deleted file mode 100644
index 9f1a810fa..000000000
--- a/example/android/AndroidExample/app/src/main/res/values/strings_activity_login.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
- Sign in
-
-
- Email
- Password (optional)
- Sign in or register
- Sign in
- Switch Google+ account
- Disconnect from Google+
- This email address is invalid
- This password is too short
- This password is incorrect
- This field is required
-
diff --git a/example/android/AndroidExample/app/src/main/res/values/styles.xml b/example/android/AndroidExample/app/src/main/res/values/styles.xml
deleted file mode 100644
index ff6c9d2c0..000000000
--- a/example/android/AndroidExample/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
diff --git a/example/android/AndroidExample/build.gradle b/example/android/AndroidExample/build.gradle
deleted file mode 100644
index 6356aabdc..000000000
--- a/example/android/AndroidExample/build.gradle
+++ /dev/null
@@ -1,19 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
-buildscript {
- repositories {
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:1.0.0'
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-allprojects {
- repositories {
- jcenter()
- }
-}
diff --git a/example/android/AndroidExample/build.gradle.kts b/example/android/AndroidExample/build.gradle.kts
new file mode 100644
index 000000000..017d90996
--- /dev/null
+++ b/example/android/AndroidExample/build.gradle.kts
@@ -0,0 +1,4 @@
+plugins {
+ id("com.android.application") version "8.5.2" apply false
+ id("org.jetbrains.kotlin.android") version "1.9.24" apply false
+}
diff --git a/example/android/AndroidExample/gradle.properties b/example/android/AndroidExample/gradle.properties
index 1d3591c8a..e6961679f 100644
--- a/example/android/AndroidExample/gradle.properties
+++ b/example/android/AndroidExample/gradle.properties
@@ -1,18 +1,3 @@
-# Project-wide Gradle settings.
-
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-# Default value: -Xmx10248m -XX:MaxPermSize=256m
-# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
-
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
\ No newline at end of file
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+android.useAndroidX=true
+kotlin.code.style=official
diff --git a/example/android/AndroidExample/gradle/wrapper/gradle-wrapper.jar b/example/android/AndroidExample/gradle/wrapper/gradle-wrapper.jar
index 8c0fb64a8..e6441136f 100644
Binary files a/example/android/AndroidExample/gradle/wrapper/gradle-wrapper.jar and b/example/android/AndroidExample/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/example/android/AndroidExample/gradle/wrapper/gradle-wrapper.properties b/example/android/AndroidExample/gradle/wrapper/gradle-wrapper.properties
index 0c71e760d..b82aa23a4 100644
--- a/example/android/AndroidExample/gradle/wrapper/gradle-wrapper.properties
+++ b/example/android/AndroidExample/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,7 @@
-#Wed Apr 10 15:27:10 PDT 2013
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.2.1-all.zip
diff --git a/example/android/AndroidExample/gradlew b/example/android/AndroidExample/gradlew
index 91a7e269e..1aa94a426 100755
--- a/example/android/AndroidExample/gradlew
+++ b/example/android/AndroidExample/gradlew
@@ -1,79 +1,127 @@
-#!/usr/bin/env bash
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
##############################################################################
-##
-## Gradle start up script for UN*X
-##
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
##############################################################################
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
+MAX_FD=maximum
-warn ( ) {
+warn () {
echo "$*"
-}
+} >&2
-die ( ) {
+die () {
echo
echo "$*"
echo
exit 1
-}
+} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
esac
-# For Cygwin, ensure paths are in UNIX format before anything is touched.
-if $cygwin ; then
- [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
-fi
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >&-
-APP_HOME="`pwd -P`"
-cd "$SAVED" >&-
-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
+ JAVACMD=$JAVA_HOME/jre/sh/java
else
- JAVACMD="$JAVA_HOME/bin/java"
+ JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -82,83 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+ JAVACMD=java
+ if ! command -v java >/dev/null 2>&1
+ then
+ die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
+ fi
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
fi
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
fi
- i=$((i+1))
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
done
- case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
fi
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
-}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/example/android/AndroidExample/gradlew.bat b/example/android/AndroidExample/gradlew.bat
index aec99730b..7101f8e46 100644
--- a/example/android/AndroidExample/gradlew.bat
+++ b/example/android/AndroidExample/gradlew.bat
@@ -1,4 +1,20 @@
-@if "%DEBUG%" == "" @echo off
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@@ -8,26 +24,30 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-set DEFAULT_JVM_OPTS=
-
set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
+if "%DIRNAME%"=="" set DIRNAME=.
+@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
+if %ERRORLEVEL% equ 0 goto execute
-echo.
-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
@@ -35,54 +55,36 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-if exist "%JAVA_EXE%" goto init
+if exist "%JAVA_EXE%" goto execute
-echo.
-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
-echo.
-echo Please set the JAVA_HOME variable in your environment to match the
-echo location of your Java installation.
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
goto fail
-:init
-@rem Get command-line arguments, handling Windowz variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-set CMD_LINE_ARGS=%$
-
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
-if "%ERRORLEVEL%"=="0" goto mainEnd
+if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
-if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
-exit /b 1
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
diff --git a/example/android/AndroidExample/kls_database.db b/example/android/AndroidExample/kls_database.db
new file mode 100644
index 000000000..4500823e1
Binary files /dev/null and b/example/android/AndroidExample/kls_database.db differ
diff --git a/example/android/AndroidExample/settings.gradle b/example/android/AndroidExample/settings.gradle
deleted file mode 100644
index e7b4def49..000000000
--- a/example/android/AndroidExample/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include ':app'
diff --git a/example/android/AndroidExample/settings.gradle.kts b/example/android/AndroidExample/settings.gradle.kts
new file mode 100644
index 000000000..110156335
--- /dev/null
+++ b/example/android/AndroidExample/settings.gradle.kts
@@ -0,0 +1,17 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "AndroidExample"
+include(":app")
diff --git a/src/core/zip/MiniZip/ioapi.c b/src/core/zip/MiniZip/ioapi.c
index 58c8fc64f..9d757e137 100644
--- a/src/core/zip/MiniZip/ioapi.c
+++ b/src/core/zip/MiniZip/ioapi.c
@@ -16,7 +16,10 @@
#include "ioapi.h"
-#if defined(__APPLE__) || defined(ANDROID) || defined(__ANDROID__)
+// On Android, ftello/fseeko are only declared at API >= 24. When USE_FILE32API is set
+// (see Android.mk) we rely on ioapi.h's 32-bit ftell/fseek mapping instead, so the build
+// works at android-21.
+#if (defined(__APPLE__) || defined(ANDROID) || defined(__ANDROID__)) && !defined(USE_FILE32API)
#define off64_t off_t
#define fopen64 fopen
#define ftello64 ftello
diff --git a/src/java/com/libmailcore/IMAPMessagesRequestKind.java b/src/java/com/libmailcore/IMAPMessagesRequestKind.java
index 20179126e..fdacf33ef 100644
--- a/src/java/com/libmailcore/IMAPMessagesRequestKind.java
+++ b/src/java/com/libmailcore/IMAPMessagesRequestKind.java
@@ -35,5 +35,5 @@ public class IMAPMessagesRequestKind {
/** Requests the size of the messages. */
public final static int IMAPMessagesRequestKindSize = 1 << 10;
/** Unlike Full headers this will fetch all the non-parsed headers */
- public final static int IMAPMessagesRequestKindSize = 1 << 11;
+ public final static int IMAPMessagesRequestKindAllHeaders = 1 << 11;
}
diff --git a/src/java/com/libmailcore/MainThreadUtils.java b/src/java/com/libmailcore/MainThreadUtils.java
index 5a5aa821a..af464be34 100644
--- a/src/java/com/libmailcore/MainThreadUtils.java
+++ b/src/java/com/libmailcore/MainThreadUtils.java
@@ -16,8 +16,9 @@ static MainThreadUtils singleton() {
// private constructor
private MainThreadUtils() {
+ // libMailCore.so statically links libc++ (APP_STL := c++_static), so it is
+ // self-contained — no separate STL .so to load (was gnustl_shared / libc++_shared).
System.loadLibrary("MailCore");
- System.loadLibrary("gnustl_shared");
handler = new Handler(Looper.getMainLooper());
setupNative();
}