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 @@ + + + + + + + + + + + +