diff --git a/platforms/android/aar-template/OpenCV/build.gradle.template b/platforms/android/aar-template/OpenCV/build.gradle.template
new file mode 100644
index 0000000000..4f3a3846ec
--- /dev/null
+++ b/platforms/android/aar-template/OpenCV/build.gradle.template
@@ -0,0 +1,88 @@
+plugins {
+ id 'com.android.library'
+ id 'maven-publish'
+}
+
+android {
+ namespace 'org.opencv'
+ compileSdk ${COMPILE_SDK}
+
+ defaultConfig {
+ minSdk ${MIN_SDK}
+ targetSdk ${TARGET_SDK}
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ externalNativeBuild {
+ cmake {
+ cppFlags ""
+ arguments "-DANDROID_STL=${LIB_TYPE}"
+ }
+ }
+ ndk {
+ abiFilters ${ABI_FILTERS}
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_${JAVA_VERSION}
+ targetCompatibility JavaVersion.VERSION_${JAVA_VERSION}
+ }
+ externalNativeBuild {
+ cmake {
+ path file('src/main/cpp/CMakeLists.txt')
+ }
+ }
+ buildFeatures {
+ aidl true
+ prefabPublishing true
+ buildConfig true
+ }
+ prefab {
+ ${LIB_NAME} {
+ headers "src/main/cpp/include"
+ }
+ }
+ sourceSets {
+ main {
+ java.srcDirs = ['src/main/java']
+ //jniLibs.srcDirs = ['libs']
+ aidl.srcDirs = ['src/main/java']
+ }
+ }
+
+ publishing {
+ singleVariant('release') {
+ withSourcesJar()
+ }
+ }
+}
+
+publishing {
+ publications {
+ release(MavenPublication) {
+ groupId = 'org.opencv'
+ artifactId = '${PACKAGE_NAME}'
+ version = '${OPENCV_VERSION}'
+ artifact("opencv-release.aar")
+
+// afterEvaluate {
+// from components.release
+// }
+ }
+ }
+ repositories {
+ maven {
+ name = 'myrepo'
+ url = "${project.buildDir}/repo"
+ }
+ }
+}
+
+dependencies {
+}
\ No newline at end of file
diff --git a/platforms/android/aar-template/OpenCV/proguard-rules.pro b/platforms/android/aar-template/OpenCV/proguard-rules.pro
new file mode 100644
index 0000000000..481bb43481
--- /dev/null
+++ b/platforms/android/aar-template/OpenCV/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# 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 *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/platforms/android/aar-template/OpenCV/src/main/AndroidManifest.xml b/platforms/android/aar-template/OpenCV/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..cce937ee78
--- /dev/null
+++ b/platforms/android/aar-template/OpenCV/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/platforms/android/aar-template/OpenCV/src/main/cpp/CMakeLists.txt.template b/platforms/android/aar-template/OpenCV/src/main/cpp/CMakeLists.txt.template
new file mode 100644
index 0000000000..02ef035e0a
--- /dev/null
+++ b/platforms/android/aar-template/OpenCV/src/main/cpp/CMakeLists.txt.template
@@ -0,0 +1,5 @@
+cmake_minimum_required(VERSION 3.6)
+
+project("opencv")
+
+add_library(${LIB_NAME} ${LIB_TYPE} native-lib.cpp)
diff --git a/platforms/android/aar-template/OpenCV/src/main/cpp/include/temp.h b/platforms/android/aar-template/OpenCV/src/main/cpp/include/temp.h
new file mode 100644
index 0000000000..4974d8ca96
--- /dev/null
+++ b/platforms/android/aar-template/OpenCV/src/main/cpp/include/temp.h
@@ -0,0 +1 @@
+// This empty .h file is used for creating an AAR with empty C++ lib that will be replaced with OpenCV C++ lib
\ No newline at end of file
diff --git a/platforms/android/aar-template/OpenCV/src/main/cpp/native-lib.cpp b/platforms/android/aar-template/OpenCV/src/main/cpp/native-lib.cpp
new file mode 100644
index 0000000000..73ac04eb7a
--- /dev/null
+++ b/platforms/android/aar-template/OpenCV/src/main/cpp/native-lib.cpp
@@ -0,0 +1 @@
+// This empty .cpp file is used for creating an AAR with empty C++ lib that will be replaced with OpenCV C++ lib
\ No newline at end of file
diff --git a/platforms/android/aar-template/README.md b/platforms/android/aar-template/README.md
new file mode 100644
index 0000000000..a851364dd4
--- /dev/null
+++ b/platforms/android/aar-template/README.md
@@ -0,0 +1,29 @@
+## Scripts for creating an AAR package and a local Maven repository with OpenCV libraries for Android
+
+### How to run the scripts
+1. Set JAVA_HOME and ANDROID_HOME enviroment variables. For example:
+```
+export JAVA_HOME=~/Android Studio/jbr
+export ANDROID_HOME=~/Android/SDK
+```
+2. Download OpenCV SDK for Android
+3. Run build script for version with Java and a shared C++ library:
+```
+python build_java_shared_aar.py "~/opencv-4.7.0-android-sdk/OpenCV-android-sdk"
+```
+4. Run build script for version with static C++ libraries:
+```
+python build_static_aar.py "~/opencv-4.7.0-android-sdk/OpenCV-android-sdk"
+```
+The AAR libraries and the local Maven repository will be created in the **outputs** directory
+### Technical details
+The scripts consist of 5 steps:
+1. Preparing Android AAR library project template
+2. Adding Java code to the project. Adding C++ public headers for shared version to the project.
+3. Compiling the project to build an AAR package
+4. Adding C++ binary libraries to the AAR package. Adding C++ public headers for static version to the AAR package.
+5. Creating Maven repository with the AAR package
+
+There are a few minor limitations:
+1. Due to the AAR design the Java + shared C++ AAR package contains duplicates of C++ binary libraries, but the final user's Android application contains only one library instance.
+2. The compile definitions from cmake configs are skipped, but it shouldn't affect the library because the script uses precompiled C++ binaries from SDK.
diff --git a/platforms/android/aar-template/build.gradle b/platforms/android/aar-template/build.gradle
new file mode 100644
index 0000000000..b7a22c372c
--- /dev/null
+++ b/platforms/android/aar-template/build.gradle
@@ -0,0 +1,4 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ id 'com.android.library' version '8.0.2' apply false
+}
\ No newline at end of file
diff --git a/platforms/android/aar-template/gradle.properties b/platforms/android/aar-template/gradle.properties
new file mode 100644
index 0000000000..3e927b11ef
--- /dev/null
+++ b/platforms/android/aar-template/gradle.properties
@@ -0,0 +1,21 @@
+# 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.
+org.gradle.jvmargs=-Xmx2048m -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
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.jar b/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..e708b1c023
Binary files /dev/null and b/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.properties b/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..9e6a7833f8
--- /dev/null
+++ b/platforms/android/aar-template/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Mon Jul 10 11:57:38 SGT 2023
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/platforms/android/aar-template/gradlew b/platforms/android/aar-template/gradlew
new file mode 100755
index 0000000000..4f906e0c81
--- /dev/null
+++ b/platforms/android/aar-template/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or 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
+##
+##############################################################################
+
+# 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\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# 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"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+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"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+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.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "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
+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
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # 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\""
+ fi
+ i=`expr $i + 1`
+ 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
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/platforms/android/aar-template/gradlew.bat b/platforms/android/aar-template/gradlew.bat
new file mode 100644
index 0000000000..107acd32c4
--- /dev/null
+++ b/platforms/android/aar-template/gradlew.bat
@@ -0,0 +1,89 @@
+@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
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+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 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.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+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.
+
+goto fail
+
+: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 %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="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
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/platforms/android/aar-template/settings.gradle b/platforms/android/aar-template/settings.gradle
new file mode 100644
index 0000000000..fb1a50602c
--- /dev/null
+++ b/platforms/android/aar-template/settings.gradle
@@ -0,0 +1,16 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+rootProject.name = "OpenCV"
+include ':OpenCV'
diff --git a/platforms/android/build_java_shared_aar.py b/platforms/android/build_java_shared_aar.py
new file mode 100755
index 0000000000..ea59bad839
--- /dev/null
+++ b/platforms/android/build_java_shared_aar.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python
+
+import argparse
+from os import path
+import os
+import re
+import shutil
+import string
+import subprocess
+
+
+COPY_FROM_SDK_TO_ANDROID_PROJECT = [
+ ["sdk/native/jni/include", "OpenCV/src/main/cpp/include"],
+ ["sdk/java/src/org", "OpenCV/src/main/java/org"],
+ ["sdk/java/res", "OpenCV/src/main/res"]
+]
+
+COPY_FROM_SDK_TO_APK = [
+ ["sdk/native/libs//lib.so", "jni//lib.so"],
+ ["sdk/native/libs//lib.so", "prefab/modules//libs/android./lib.so"],
+]
+
+ANDROID_PROJECT_TEMPLATE_DIR = path.join(path.dirname(__file__), "aar-template")
+TEMP_DIR = "build_java_shared"
+ANDROID_PROJECT_DIR = path.join(TEMP_DIR, "AndroidProject")
+COMPILED_AAR_PATH_1 = path.join(ANDROID_PROJECT_DIR, "OpenCV/build/outputs/aar/OpenCV-release.aar") # original package name
+COMPILED_AAR_PATH_2 = path.join(ANDROID_PROJECT_DIR, "OpenCV/build/outputs/aar/opencv-release.aar") # lower case package name
+AAR_UNZIPPED_DIR = path.join(TEMP_DIR, "aar_unzipped")
+FINAL_AAR_PATH_TEMPLATE = "outputs/opencv_java_shared_.aar"
+FINAL_REPO_PATH = "outputs/maven_repo"
+MAVEN_PACKAGE_NAME = "opencv"
+
+def fill_template(src_path, dst_path, args_dict):
+ with open(src_path, "r") as f:
+ template_text = f.read()
+ template = string.Template(template_text)
+ text = template.safe_substitute(args_dict)
+ with open(dst_path, "w") as f:
+ f.write(text)
+
+def get_opencv_version(opencv_sdk_path):
+ version_hpp_path = path.join(opencv_sdk_path, "sdk/native/jni/include/opencv2/core/version.hpp")
+ with open(version_hpp_path, "rt") as f:
+ data = f.read()
+ major = re.search(r'^#define\W+CV_VERSION_MAJOR\W+(\d+)$', data, re.MULTILINE).group(1)
+ minor = re.search(r'^#define\W+CV_VERSION_MINOR\W+(\d+)$', data, re.MULTILINE).group(1)
+ revision = re.search(r'^#define\W+CV_VERSION_REVISION\W+(\d+)$', data, re.MULTILINE).group(1)
+ return "%(major)s.%(minor)s.%(revision)s" % locals()
+
+def get_compiled_aar_path(path1, path2):
+ if path.exists(path1):
+ return path1
+ elif path.exists(path2):
+ return path2
+ else:
+ raise Exception("Can't find compiled AAR path in [" + path1 + ", " + path2 + "]")
+
+def cleanup(paths_to_remove):
+ exists = False
+ for p in paths_to_remove:
+ if path.exists(p):
+ exists = True
+ if path.isdir(p):
+ shutil.rmtree(p)
+ else:
+ os.remove(p)
+ print("Removed", p)
+ if not exists:
+ print("Nothing to remove")
+
+def main(args):
+ opencv_version = get_opencv_version(args.opencv_sdk_path)
+ abis = os.listdir(path.join(args.opencv_sdk_path, "sdk/native/libs"))
+ lib_name = "opencv_java" + opencv_version.split(".")[0]
+ final_aar_path = FINAL_AAR_PATH_TEMPLATE.replace("", opencv_version)
+
+ print("Removing data from previous runs...")
+ cleanup([TEMP_DIR, final_aar_path, path.join(FINAL_REPO_PATH, "org/opencv", MAVEN_PACKAGE_NAME)])
+
+ print("Preparing Android project...")
+ # ANDROID_PROJECT_TEMPLATE_DIR contains an Android project template that creates AAR
+ shutil.copytree(ANDROID_PROJECT_TEMPLATE_DIR, ANDROID_PROJECT_DIR)
+
+ # Configuring the Android project to Java + shared C++ lib version
+ shutil.rmtree(path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/include"))
+
+ fill_template(path.join(ANDROID_PROJECT_DIR, "OpenCV/build.gradle.template"),
+ path.join(ANDROID_PROJECT_DIR, "OpenCV/build.gradle"),
+ {"LIB_NAME": lib_name,
+ "LIB_TYPE": "c++_shared",
+ "PACKAGE_NAME": MAVEN_PACKAGE_NAME,
+ "OPENCV_VERSION": opencv_version,
+ "COMPILE_SDK": args.android_compile_sdk,
+ "MIN_SDK": args.android_min_sdk,
+ "TARGET_SDK": args.android_target_sdk,
+ "ABI_FILTERS": ", ".join(['"' + x + '"' for x in abis]),
+ "JAVA_VERSION": args.java_version,
+ })
+ fill_template(path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/CMakeLists.txt.template"),
+ path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/CMakeLists.txt"),
+ {"LIB_NAME": lib_name, "LIB_TYPE": "SHARED"})
+
+ # Copying Java code and C++ public headers from SDK to the Android project
+ for src, dst in COPY_FROM_SDK_TO_ANDROID_PROJECT:
+ shutil.copytree(path.join(args.opencv_sdk_path, src),
+ path.join(ANDROID_PROJECT_DIR, dst))
+
+ print("Running gradle assembleRelease...")
+ # Running gradle to build the Android project
+ subprocess.run(["./gradlew", "assembleRelease"],
+ shell=False,
+ cwd=ANDROID_PROJECT_DIR,
+ check=True)
+
+ print("Adding libs to AAR...")
+ # The created AAR package doesn't contain C++ shared libs.
+ # We need to add them manually.
+ # AAR package is just a zip archive.
+ complied_aar_path = get_compiled_aar_path(COMPILED_AAR_PATH_1, COMPILED_AAR_PATH_2) # two possible paths
+ shutil.unpack_archive(complied_aar_path, AAR_UNZIPPED_DIR, "zip")
+
+ for abi in abis:
+ for src, dst in COPY_FROM_SDK_TO_APK:
+ src = src.replace("", abi).replace("", lib_name)
+ dst = dst.replace("", abi).replace("", lib_name)
+ shutil.copy(path.join(args.opencv_sdk_path, src),
+ path.join(AAR_UNZIPPED_DIR, dst))
+
+ # Creating final AAR zip archive
+ os.makedirs("outputs", exist_ok=True)
+ shutil.make_archive(final_aar_path, "zip", AAR_UNZIPPED_DIR, ".")
+ os.rename(final_aar_path + ".zip", final_aar_path)
+
+ print("Creating local maven repo...")
+
+ shutil.copy(final_aar_path, path.join(ANDROID_PROJECT_DIR, "OpenCV/opencv-release.aar"))
+ subprocess.run(["./gradlew", "publishReleasePublicationToMyrepoRepository"],
+ shell=False,
+ cwd=ANDROID_PROJECT_DIR,
+ check=True)
+
+ os.makedirs(path.join(FINAL_REPO_PATH, "org/opencv"), exist_ok=True)
+ shutil.move(path.join(ANDROID_PROJECT_DIR, "OpenCV/build/repo/org/opencv", MAVEN_PACKAGE_NAME),
+ path.join(FINAL_REPO_PATH, "org/opencv", MAVEN_PACKAGE_NAME))
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Builds AAR with Java and shared C++ libs from OpenCV SDK")
+ parser.add_argument('opencv_sdk_path')
+ parser.add_argument('--android_compile_sdk', default="26")
+ parser.add_argument('--android_min_sdk', default="21")
+ parser.add_argument('--android_target_sdk', default="26")
+ parser.add_argument('--java_version', default="1_8")
+ args = parser.parse_args()
+
+ main(args)
diff --git a/platforms/android/build_static_aar.py b/platforms/android/build_static_aar.py
new file mode 100755
index 0000000000..df6d55a6fd
--- /dev/null
+++ b/platforms/android/build_static_aar.py
@@ -0,0 +1,229 @@
+#!/usr/bin/env python
+
+import argparse
+import json
+from os import path
+import os
+import shutil
+import subprocess
+
+from build_java_shared_aar import cleanup, fill_template, get_compiled_aar_path, get_opencv_version
+
+
+ANDROID_PROJECT_TEMPLATE_DIR = path.join(path.dirname(__file__), "aar-template")
+TEMP_DIR = "build_static"
+ANDROID_PROJECT_DIR = path.join(TEMP_DIR, "AndroidProject")
+COMPILED_AAR_PATH_1 = path.join(ANDROID_PROJECT_DIR, "OpenCV/build/outputs/aar/OpenCV-release.aar") # original package name
+COMPILED_AAR_PATH_2 = path.join(ANDROID_PROJECT_DIR, "OpenCV/build/outputs/aar/opencv-release.aar") # lower case package name
+AAR_UNZIPPED_DIR = path.join(TEMP_DIR, "aar_unzipped")
+FINAL_AAR_PATH_TEMPLATE = "outputs/opencv_static_.aar"
+FINAL_REPO_PATH = "outputs/maven_repo"
+MAVEN_PACKAGE_NAME = "opencv-static"
+
+
+def get_list_of_opencv_libs(sdk_dir):
+ files = os.listdir(path.join(sdk_dir, "sdk/native/staticlibs/arm64-v8a"))
+ libs = [f[3:-2] for f in files if f[:3] == "lib" and f[-2:] == ".a"]
+ return libs
+
+def get_list_of_3rdparty_libs(sdk_dir, abis):
+ libs = []
+ for abi in abis:
+ files = os.listdir(path.join(sdk_dir, "sdk/native/3rdparty/libs/" + abi))
+ cur_libs = [f[3:-2] for f in files if f[:3] == "lib" and f[-2:] == ".a"]
+ for lib in cur_libs:
+ if lib not in libs:
+ libs.append(lib)
+ return libs
+
+def add_printing_linked_libs(sdk_dir, opencv_libs):
+ """
+ Modifies CMakeLists.txt file in Android project, so it prints linked libraries for each OpenCV library"
+ """
+ sdk_jni_dir = sdk_dir + "/sdk/native/jni"
+ with open(path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/CMakeLists.txt"), "a") as f:
+ f.write('\nset(OpenCV_DIR "' + sdk_jni_dir + '")\n')
+ f.write('find_package(OpenCV REQUIRED)\n')
+ for lib_name in opencv_libs:
+ output_filename_prefix = "linkedlibs." + lib_name + "."
+ f.write('get_target_property(OUT "' + lib_name + '" INTERFACE_LINK_LIBRARIES)\n')
+ f.write('file(WRITE "' + output_filename_prefix + '${ANDROID_ABI}.txt" "${OUT}")\n')
+
+def read_linked_libs(lib_name, abis):
+ """
+ Reads linked libs for each OpenCV library from files, that was generated by gradle. See add_printing_linked_libs()
+ """
+ deps_lists = []
+ for abi in abis:
+ with open(path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp", f"linkedlibs.{lib_name}.{abi}.txt")) as f:
+ text = f.read()
+ linked_libs = text.split(";")
+ linked_libs = [x.replace("$", "") for x in linked_libs]
+ deps_lists.append(linked_libs)
+
+ return merge_dependencies_lists(deps_lists)
+
+def merge_dependencies_lists(deps_lists):
+ """
+ One library may have different dependencies for different ABIS.
+ We need to merge them into one list with all the dependencies preserving the order.
+ """
+ result = []
+ for d_list in deps_lists:
+ for i in range(len(d_list)):
+ if d_list[i] not in result:
+ if i == 0:
+ result.append(d_list[i])
+ else:
+ index = result.index(d_list[i-1])
+ result = result[:index + 1] + [d_list[i]] + result[index + 1:]
+
+ return result
+
+def convert_deps_list_to_prefab(linked_libs, opencv_libs, external_libs):
+ """
+ Converting list of dependencies into prefab format.
+ """
+ prefab_linked_libs = []
+ for lib in linked_libs:
+ if (lib in opencv_libs) or (lib in external_libs):
+ prefab_linked_libs.append(":" + lib)
+ elif (lib[:3] == "lib" and lib[3:] in external_libs):
+ prefab_linked_libs.append(":" + lib[3:])
+ elif lib == "ocv.3rdparty.android_mediandk":
+ prefab_linked_libs += ["-landroid", "-llog", "-lmediandk"]
+ print("Warning: manualy handled ocv.3rdparty.android_mediandk dependency")
+ elif lib == "ocv.3rdparty.flatbuffers":
+ print("Warning: manualy handled ocv.3rdparty.flatbuffers dependency")
+ elif lib.startswith("ocv.3rdparty"):
+ raise Exception("Unknown lib " + lib)
+ else:
+ prefab_linked_libs.append("-l" + lib)
+ return prefab_linked_libs
+
+def main(args):
+ opencv_version = get_opencv_version(args.opencv_sdk_path)
+ abis = os.listdir(path.join(args.opencv_sdk_path, "sdk/native/libs"))
+ final_aar_path = FINAL_AAR_PATH_TEMPLATE.replace("", opencv_version)
+ sdk_dir = args.opencv_sdk_path
+
+ print("Removing data from previous runs...")
+ cleanup([TEMP_DIR, final_aar_path, path.join(FINAL_REPO_PATH, "org/opencv", MAVEN_PACKAGE_NAME)])
+
+ print("Preparing Android project...")
+ # ANDROID_PROJECT_TEMPLATE_DIR contains an Android project template that creates AAR
+ shutil.copytree(ANDROID_PROJECT_TEMPLATE_DIR, ANDROID_PROJECT_DIR)
+
+ # Configuring the Android project to static C++ libs version
+ fill_template(path.join(ANDROID_PROJECT_DIR, "OpenCV/build.gradle.template"),
+ path.join(ANDROID_PROJECT_DIR, "OpenCV/build.gradle"),
+ {"LIB_NAME": "templib",
+ "LIB_TYPE": "c++_static",
+ "PACKAGE_NAME": MAVEN_PACKAGE_NAME,
+ "OPENCV_VERSION": opencv_version,
+ "COMPILE_SDK": args.android_compile_sdk,
+ "MIN_SDK": args.android_min_sdk,
+ "TARGET_SDK": args.android_target_sdk,
+ "ABI_FILTERS": ", ".join(['"' + x + '"' for x in abis]),
+ "JAVA_VERSION": args.java_version,
+ })
+ fill_template(path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/CMakeLists.txt.template"),
+ path.join(ANDROID_PROJECT_DIR, "OpenCV/src/main/cpp/CMakeLists.txt"),
+ {"LIB_NAME": "templib", "LIB_TYPE": "STATIC"})
+
+ opencv_libs = get_list_of_opencv_libs(sdk_dir)
+ external_libs = get_list_of_3rdparty_libs(sdk_dir, abis)
+
+ add_printing_linked_libs(sdk_dir, opencv_libs)
+
+ print("Running gradle assembleRelease...")
+ # Running gradle to build the Android project
+ subprocess.run(["./gradlew", "assembleRelease"],
+ shell=False,
+ cwd=ANDROID_PROJECT_DIR,
+ check=True)
+
+ # The created AAR package contains only one empty libtemplib.a library.
+ # We need to add OpenCV libraries manually.
+ # AAR package is just a zip archive
+ complied_aar_path = get_compiled_aar_path(COMPILED_AAR_PATH_1, COMPILED_AAR_PATH_2) # two possible paths
+ shutil.unpack_archive(complied_aar_path, AAR_UNZIPPED_DIR, "zip")
+
+ print("Adding libs to AAR...")
+
+ # Copying 3rdparty libs from SDK into the AAR
+ for lib in external_libs:
+ for abi in abis:
+ os.makedirs(path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi))
+ if path.exists(path.join(sdk_dir, "sdk/native/3rdparty/libs/" + abi, "lib" + lib + ".a")):
+ shutil.copy(path.join(sdk_dir, "sdk/native/3rdparty/libs/" + abi, "lib" + lib + ".a"),
+ path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi, "lib" + lib + ".a"))
+ else:
+ # One OpenCV library may have different dependency lists for different ABIs, but we can write only one
+ # full dependency list for all ABIs. So we just add empty .a library if this ABI doesn't have this dependency.
+ shutil.copy(path.join(AAR_UNZIPPED_DIR, "prefab/modules/templib/libs/android." + abi, "libtemplib.a"),
+ path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi, "lib" + lib + ".a"))
+ shutil.copy(path.join(AAR_UNZIPPED_DIR, "prefab/modules/templib/libs/android." + abi + "/abi.json"),
+ path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi + "/abi.json"))
+ shutil.copy(path.join(AAR_UNZIPPED_DIR, "prefab/modules/templib/module.json"),
+ path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/module.json"))
+
+ # Copying OpenV libs from SDK into the AAR
+ for lib in opencv_libs:
+ for abi in abis:
+ os.makedirs(path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi))
+ shutil.copy(path.join(sdk_dir, "sdk/native/staticlibs/" + abi, "lib" + lib + ".a"),
+ path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi, "lib" + lib + ".a"))
+ shutil.copy(path.join(AAR_UNZIPPED_DIR, "prefab/modules/templib/libs/android." + abi + "/abi.json"),
+ path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/libs/android." + abi + "/abi.json"))
+ os.makedirs(path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/include/opencv2"))
+ shutil.copy(path.join(sdk_dir, "sdk/native/jni/include/opencv2/" + lib.replace("opencv_", "") + ".hpp"),
+ path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/include/opencv2/" + lib.replace("opencv_", "") + ".hpp"))
+ shutil.copytree(path.join(sdk_dir, "sdk/native/jni/include/opencv2/" + lib.replace("opencv_", "")),
+ path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/include/opencv2/" + lib.replace("opencv_", "")))
+
+ # Adding dependencies list
+ module_json_text = {
+ "export_libraries": convert_deps_list_to_prefab(read_linked_libs(lib, abis), opencv_libs, external_libs),
+ "android": {},
+ }
+ with open(path.join(AAR_UNZIPPED_DIR, "prefab/modules/" + lib + "/module.json"), "w") as f:
+ json.dump(module_json_text, f)
+
+ for h_file in ("cvconfig.h", "opencv.hpp", "opencv_modules.hpp"):
+ shutil.copy(path.join(sdk_dir, "sdk/native/jni/include/opencv2/" + h_file),
+ path.join(AAR_UNZIPPED_DIR, "prefab/modules/opencv_core/include/opencv2/" + h_file))
+
+
+ shutil.rmtree(path.join(AAR_UNZIPPED_DIR, "prefab/modules/templib"))
+
+ # Creating final AAR zip archive
+ os.makedirs("outputs", exist_ok=True)
+ shutil.make_archive(final_aar_path, "zip", AAR_UNZIPPED_DIR, ".")
+ os.rename(final_aar_path + ".zip", final_aar_path)
+
+ print("Creating local maven repo...")
+
+ shutil.copy(final_aar_path, path.join(ANDROID_PROJECT_DIR, "OpenCV/opencv-release.aar"))
+
+ subprocess.run(["./gradlew", "publishReleasePublicationToMyrepoRepository"],
+ shell=False,
+ cwd=ANDROID_PROJECT_DIR,
+ check=True)
+
+ os.makedirs(path.join(FINAL_REPO_PATH, "org/opencv"), exist_ok=True)
+ shutil.move(path.join(ANDROID_PROJECT_DIR, "OpenCV/build/repo/org/opencv", MAVEN_PACKAGE_NAME),
+ path.join(FINAL_REPO_PATH, "org/opencv", MAVEN_PACKAGE_NAME))
+ print("Done")
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Builds AAR with static C++ libs from OpenCV SDK")
+ parser.add_argument('opencv_sdk_path')
+ parser.add_argument('--android_compile_sdk', default="26")
+ parser.add_argument('--android_min_sdk', default="21")
+ parser.add_argument('--android_target_sdk', default="26")
+ parser.add_argument('--java_version', default="1_8")
+ args = parser.parse_args()
+
+ main(args)