├── Sources ├── assets │ ├── asset.txt │ ├── profile-picture.bmp │ └── test.html └── res │ ├── mipmap │ └── icon.png │ └── values │ └── strings.xml ├── docker ├── bg.sh ├── Dockerfile ├── README.md └── rda.ps1 ├── .gitignore ├── images └── sample.png ├── .github ├── dependabot.yml └── workflows │ └── install.yml ├── .gitmodules ├── android_usb_devices.h ├── AndroidManifest.xml.template ├── LICENSE ├── Makefile ├── clay_renderer_cnfg.c ├── android_usb_devices.c ├── android_native_app_glue.h ├── webview_native_activity.h ├── README.md ├── android_native_app_glue.c └── test.c /Sources/assets/asset.txt: -------------------------------------------------------------------------------- 1 | Test asset file 2 | -------------------------------------------------------------------------------- /docker/bg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while :; do sleep 0.5; done; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.keystore 2 | makecapk/ 3 | cnfgtest.apk 4 | AndroidManifest.xml 5 | -------------------------------------------------------------------------------- /images/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmmanuelMess/Clay-Android/HEAD/images/sample.png -------------------------------------------------------------------------------- /Sources/res/mipmap/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmmanuelMess/Clay-Android/HEAD/Sources/res/mipmap/icon.png -------------------------------------------------------------------------------- /Sources/assets/profile-picture.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmmanuelMess/Clay-Android/HEAD/Sources/assets/profile-picture.bmp -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | 8 | -------------------------------------------------------------------------------- /Sources/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | cnfgtest 4 | cnfgtest 5 | org.yourorg.cnfgtest 6 | 7 | 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "rawdraw"] 2 | path = rawdraw 3 | url = https://github.com/cntools/rawdraw 4 | [submodule "cnfa"] 5 | path = cnfa 6 | url = https://github.com/cntools/cnfa 7 | [submodule "clay"] 8 | path = clay 9 | url = https://github.com/nicbarker/clay.git 10 | -------------------------------------------------------------------------------- /android_usb_devices.h: -------------------------------------------------------------------------------- 1 | //Copyright 2020 <>< Charles Lohr, You may use this file and library freely under the MIT/x11, NewBSD or ColorChord Licenses. 2 | 3 | #ifndef _ANDROID_USB_DEVICES_H 4 | #define _ANDROID_USB_DEVICES_H 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | int RequestPermissionOrGetConnectionFD( char * debug_status, uint16_t vid, uint16_t pid ); 11 | void DisconnectUSB(); //Disconnect from USB 12 | 13 | extern jobject deviceConnection; 14 | extern int deviceConnectionFD; 15 | 16 | #endif 17 | 18 | -------------------------------------------------------------------------------- /Sources/assets/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 33 | 34 | 35 | Test page 36 |
37 |
38 |
39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /AndroidManifest.xml.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 <>< CNLohr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | RUN apt-get update \ 4 | && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ 5 | build-essential \ 6 | openjdk-11-jdk-headless \ 7 | unzip \ 8 | wget \ 9 | zip \ 10 | && apt-get autoremove -y \ 11 | && apt-get clean \ 12 | && rm /var/lib/apt/lists/* -r \ 13 | && rm -rf /usr/share/man/* 14 | 15 | RUN useradd -ms /bin/bash droid \ 16 | && adduser droid sudo \ 17 | && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers \ 18 | && printf "\nexport ANDROID_HOME=~/android-sdk\n" >> /home/droid/.bashrc \ 19 | && printf '#!/bin/bash\n/home/droid/android-sdk/platform-tools/adb -H host.docker.internal "$@"\n' > /usr/local/bin/adb \ 20 | && chmod +x /usr/local/bin/adb 21 | 22 | COPY ./bg.sh / 23 | 24 | ENV ANDROID_HOME=/home/droid/android-sdk 25 | ENV URL_CLITOOLS=https://dl.google.com/android/repository/commandlinetools-linux-6200805_latest.zip 26 | 27 | USER droid 28 | 29 | ENV TMPFILE="/home/droid/temp.zip" 30 | 31 | RUN mkdir ${ANDROID_HOME} \ 32 | && wget --quiet ${URL_CLITOOLS} -O ${TMPFILE} \ 33 | && unzip -d ${ANDROID_HOME} ${TMPFILE} \ 34 | && rm ${TMPFILE} \ 35 | && yes | $ANDROID_HOME/tools/bin/sdkmanager --sdk_root=${ANDROID_HOME} --licenses \ 36 | && $ANDROID_HOME/tools/bin/sdkmanager --sdk_root=${ANDROID_HOME} "build-tools;29.0.3" "cmake;3.10.2.4988404" "ndk;21.1.6352462" "patcher;v4" "platform-tools" "platforms;android-29" "tools" 37 | -------------------------------------------------------------------------------- /.github/workflows/install.yml: -------------------------------------------------------------------------------- 1 | name: rawdrawandroid 2 | on: 3 | push: 4 | branches: 5 | - '**' # All branches for now 6 | pull_request: 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | # Can we do this without a custom "container:"? 11 | env: 12 | ANDROID_HOME: /home/droid/android-sdk 13 | SDKMANAGER: /home/droid/android-sdk/cmdline-tools/bin/sdkmanager 14 | TMPFILE: /home/droid/temp.zip 15 | URL_CLITOOLS: https://dl.google.com/android/repository/commandlinetools-linux-6858069_latest.zip 16 | steps: 17 | - uses: actions/checkout@v6 18 | with: 19 | submodules: recursive 20 | - name: Install dependencies (apt) 21 | run: sudo apt-get update && sudo apt-get install -y --no-install-recommends 22 | build-essential 23 | openjdk-11-jdk-headless 24 | unzip 25 | wget 26 | zip 27 | git 28 | vim 29 | gettext-base 30 | 31 | - name: Download Android tooling 32 | run: sudo mkdir -p ${ANDROID_HOME} 33 | && sudo wget --quiet ${URL_CLITOOLS} -O ${TMPFILE} 34 | && sudo unzip -d ${ANDROID_HOME} ${TMPFILE} 35 | && sudo rm ${TMPFILE} 36 | 37 | - name: Install Android tooling 38 | run: yes | sudo $SDKMANAGER --sdk_root=${ANDROID_HOME} --licenses 39 | && sudo $SDKMANAGER --sdk_root=${ANDROID_HOME} 40 | "build-tools;29.0.3" 41 | "cmake;3.10.2.4988404" 42 | "ndk;22.1.7171670" 43 | "platform-tools" 44 | "platforms;android-30" 45 | "tools" 46 | # This list can probably be reduced 47 | - name: Build APK 48 | run: make keystore && make 49 | - name: Upload artifacts 50 | uses: actions/upload-artifact@v5 51 | with: 52 | name: apk 53 | path: ./*.apk 54 | 55 | - name: Upload Libraries (*.so) 56 | uses: actions/upload-artifact@v5 57 | with: 58 | name: lib 59 | path: "makecapk/lib" 60 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | # rawdrawandroid Docker image 2 | 3 | Docker image containing all tools for building rawdrawandroid projects. Host system needs to have only `adb` installed, everything else is inside Docker image. Host system `adb` is used as a server and `adb` inside running container is connected to this server in client mode using `adb -H host.docker.internal` parameter. 4 | 5 | ## Usage in Windows 6 | 7 | * You need to have `adb` installed on your host Windows. `adb` **versions on host system and inside Docker container MUST be the same**. Usually rebuilding Docker image and updating platform-tools on host system will ensure equal `adb` versions. 8 | * Edit `rda.ps1` helper script and update `$CmdAdb` so that it points to `adb` executable on your system. 9 | * Connect Android device to host system and check that host `adb` can detect it: 10 | 11 | ```powershell 12 | > adb devices -l 13 | List of devices attached 14 | bcc4c4e1 device product:lineage_TBX704 model:Lenovo_TB_X704F device:X704F transport_id:3 15 | ``` 16 | 17 | * Build Docker image: 18 | 19 | ```powershell 20 | cd docker 21 | .\rda.ps1 -Build 22 | cd .. 23 | ``` 24 | 25 | * Check that `adb` inside container can connect to host `adb` and your Android device: 26 | 27 | ```powershell 28 | > .\docker\rda.ps1 -Run adb devices -l 29 | List of devices attached 30 | bcc4c4e1 device product:lineage_TBX704 model:Lenovo_TB_X704F device:X704F transport_id:3 31 | ``` 32 | 33 | ### Running commands 34 | 35 | Run `make` commands in powershell console by prefixing them with `.\docker\rda.ps1 -Run`. Adjust path to `rda.ps1` as necessary or copy `rda.ps1` to your project folder. 36 | 37 | You can either run container separately for every command: 38 | 39 | ```powershell 40 | > .\docker\rda.ps1 -Run make run 41 | ``` 42 | 43 | Or you can preload container and leave it running in background. This way you will avoid delays in creating and tearing down of the container. 44 | 45 | ```powershell 46 | > .\docker\rda.ps1 -Start 47 | ``` 48 | 49 | Execute as many commands as you need: 50 | 51 | ```powershell 52 | > .\docker\rda.ps1 -Exec make run 53 | ``` 54 | 55 | And finally stop container: 56 | 57 | ```powershell 58 | > .\docker\rda.ps1 -Stop 59 | ``` 60 | 61 | Note: Commands executed using both `-Run` and `-Exec` are run in the current working directory. 62 | -------------------------------------------------------------------------------- /docker/rda.ps1: -------------------------------------------------------------------------------- 1 | # rawdrawandroid Docker image Windows helper script 2 | 3 | [CmdletBinding(PositionalBinding = $false)] 4 | 5 | param 6 | ( 7 | # Switch: build image 8 | [switch]$Build, 9 | 10 | # Switch: remove rawdrawandroid Docker images 11 | [switch]$Clean, 12 | 13 | # Switch: execute command in running container 14 | [switch]$Exec, 15 | 16 | # Switch: run container and execute command 17 | [switch]$Run, 18 | 19 | # Switch: start container and leave it running in background 20 | [switch]$Start, 21 | 22 | # Switch: stop container running in background 23 | [switch]$Stop, 24 | 25 | # Image name 26 | [ValidateNotNullOrEmpty()] 27 | [string]$ImageName = 'rawdrawandroid', 28 | 29 | # Image tag 30 | [ValidateNotNullOrEmpty()] 31 | [string]$ImageTag = '0.0.1', 32 | 33 | # Remaining arguments 34 | [string[]] 35 | [Parameter(ValueFromRemainingArguments)] 36 | $Passthrough 37 | ) 38 | 39 | $ErrorActionPreference = 'Stop' 40 | 41 | # Path to adb executable 42 | # !! EDIT THIS according to your system adb location !! 43 | $CmdAdb = 'c:\Users\YOUR_USERNAME\AppData\Local\Android\Sdk\platform-tools\adb' 44 | 45 | # Path to Docker executable on your system 46 | $CmdDocker = 'docker' 47 | 48 | $Cwd = Get-Location 49 | 50 | function Build() { 51 | # Build Docker image 52 | $argDocker = 53 | 'build', 54 | '--pull', 55 | '-t', "${ImageName}:${ImageTag}", 56 | '-f', "./Dockerfile", 57 | '.' 58 | & $CmdDocker $argDocker 59 | } 60 | 61 | function Run() { 62 | # Run Docker container 63 | # - make sure host ADB server is running 64 | & $CmdAdb start-server 65 | # - run container 66 | $argDocker = 67 | 'run', 68 | '-it', 69 | '--rm', 70 | '--name', "${ImageName}", 71 | '--network', 'host', 72 | '--volume', "${Cwd}:/src", 73 | '--workdir', '/src', 74 | "${ImageName}:${ImageTag}" 75 | & $CmdDocker $argDocker @Passthrough 76 | } 77 | 78 | function StartBg() { 79 | # Start container and leave it running in background 80 | $argDocker = 81 | 'run', 82 | '-d', 83 | '-t', 84 | '--rm', 85 | '--name', "${ImageName}", 86 | '--network', 'host', 87 | '--volume', "${Cwd}:/src", 88 | '--workdir', '/src', 89 | '--entrypoint', '/bg.sh', 90 | "${imageName}:${imageTag}" 91 | & $CmdDocker $argDocker 92 | } 93 | 94 | function StopBg() { 95 | # Stop container running in background 96 | $argDocker = 97 | 'stop', 98 | "${ImageName}" 99 | & $CmdDocker $argDocker 100 | } 101 | 102 | function Exec() { 103 | # Run a command in a running container 104 | # - make sure host ADB server is running 105 | & $CmdAdb start-server 106 | # - execute command 107 | $argDocker = 108 | 'exec', 109 | '-t', 110 | "${ImageName}" 111 | & $CmdDocker $argDocker @Passthrough 112 | } 113 | 114 | function Clean() { 115 | # Remove rawdrawandroid Docker images 116 | $ImageNameRegEx = '\b$ImageName\b' 117 | docker images | Select-String -Pattern $ImageNameRegEx | 118 | ForEach-Object { 119 | $iName = $_.Line.split(" ", [System.StringSplitOptions]::RemoveEmptyEntries)[0]; 120 | $iTag = $_.Line.split(" ", [System.StringSplitOptions]::RemoveEmptyEntries)[1]; 121 | Write-Host 'Removing image ${iName}:${iTag}'; 122 | docker rmi ${iName}:${iTag} --force 123 | } 124 | } 125 | 126 | if ($Build) { 127 | Build 128 | } 129 | 130 | if ($Clean) { 131 | Clean 132 | } 133 | 134 | if ($Exec) { 135 | Exec 136 | } 137 | 138 | if ($Start) { 139 | StartBg 140 | } 141 | 142 | if ($Stop) { 143 | StopBg 144 | } 145 | 146 | if ($Run) { 147 | Run 148 | } 149 | 150 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #Copyright (c) 2019-2020 <>< Charles Lohr - Under the MIT/x11 or NewBSD License you choose. 2 | # NO WARRANTY! NO GUARANTEE OF SUPPORT! USE AT YOUR OWN RISK 3 | 4 | all : makecapk.apk 5 | 6 | .PHONY : push run 7 | 8 | # WARNING WARNING WARNING! YOU ABSOLUTELY MUST OVERRIDE THE PROJECT NAME 9 | # you should also override these parameters, get your own signatre file and make your own manifest. 10 | APPNAME?=cnfgtest 11 | LABEL?=$(APPNAME) 12 | APKFILE ?= $(APPNAME).apk 13 | PACKAGENAME?=org.yourorg.$(APPNAME) 14 | RAWDRAWANDROID?=. 15 | RAWDRAWANDROIDSRCS=$(RAWDRAWANDROID)/android_native_app_glue.c 16 | SRC?=test.c 17 | 18 | #We've tested it with android version 22, 24, 28, 29 and 30 and 32. 19 | #You can target something like Android 28, but if you set ANDROIDVERSION to say 22, then 20 | #Your app should (though not necessarily) support all the way back to Android 22. 21 | ANDROIDVERSION?=30 22 | ANDROIDTARGET?=$(ANDROIDVERSION) 23 | CFLAGS?=-ffunction-sections -Os -fdata-sections -Wall -fvisibility=hidden 24 | LDFLAGS?=-Wl,--gc-sections -Wl,-Map=output.map -s 25 | ANDROID_FULLSCREEN?=y 26 | ADB?=adb 27 | UNAME := $(shell uname) 28 | 29 | # For really tight compiles.... 30 | CFLAGS += -fvisibility=hidden 31 | LDFLAGS += -s 32 | 33 | # For C++ 34 | # LDFLAGS += -static-libstdc++ 35 | # $(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/sysroot/usr/lib/aarch64-linux-android/libc.a 36 | 37 | ANDROID_FULLSCREEN?=y 38 | ADB?=adb 39 | UNAME := $(shell uname) 40 | 41 | 42 | ANDROIDSRCS:= $(SRC) $(RAWDRAWANDROIDSRCS) 43 | 44 | #if you have a custom Android Home location you can add it to this list. 45 | #This makefile will select the first present folder. 46 | 47 | 48 | ifeq ($(UNAME), Linux) 49 | OS_NAME = linux-x86_64 50 | endif 51 | ifeq ($(UNAME), Darwin) 52 | OS_NAME = darwin-x86_64 53 | endif 54 | ifeq ($(OS), Windows_NT) 55 | OS_NAME = windows-x86_64 56 | endif 57 | 58 | # Search list for where to try to find the SDK 59 | SDK_LOCATIONS += $(ANDROID_HOME) $(ANDROID_SDK_ROOT) ~/Android/Sdk $(HOME)/Library/Android/sdk 60 | 61 | #Just a little Makefile witchcraft to find the first SDK_LOCATION that exists 62 | #Then find an ndk folder and build tools folder in there. 63 | ANDROIDSDK?=$(firstword $(foreach dir, $(SDK_LOCATIONS), $(basename $(dir) ) ) ) 64 | NDK?=$(firstword $(ANDROID_NDK) $(ANDROID_NDK_HOME) $(wildcard $(ANDROIDSDK)/ndk/*) $(wildcard $(ANDROIDSDK)/ndk-bundle/*) ) 65 | BUILD_TOOLS?=$(lastword $(wildcard $(ANDROIDSDK)/build-tools/*) ) 66 | 67 | # fall back to default Android SDL installation location if valid NDK was not found 68 | ifeq ($(NDK),) 69 | ANDROIDSDK := ~/Android/Sdk 70 | endif 71 | 72 | # Verify if directories are detected 73 | ifeq ($(ANDROIDSDK),) 74 | $(error ANDROIDSDK directory not found) 75 | endif 76 | ifeq ($(NDK),) 77 | $(error NDK directory not found) 78 | endif 79 | ifeq ($(BUILD_TOOLS),) 80 | $(error BUILD_TOOLS directory not found) 81 | endif 82 | 83 | testsdk : 84 | @echo "SDK:\t\t" $(ANDROIDSDK) 85 | @echo "NDK:\t\t" $(NDK) 86 | @echo "Build Tools:\t" $(BUILD_TOOLS) 87 | 88 | CFLAGS+=-Os -DANDROID -DAPPNAME=\"$(APPNAME)\" 89 | ifeq (ANDROID_FULLSCREEN,y) 90 | CFLAGS +=-DANDROID_FULLSCREEN 91 | endif 92 | CFLAGS+= -I$(RAWDRAWANDROID)/rawdraw -I$(RAWDRAWANDROID)/clay -I$(NDK)/sysroot/usr/include -I$(NDK)/sysroot/usr/include/android -I$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/sysroot/usr/include -I$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/sysroot/usr/include/android -fPIC -I$(RAWDRAWANDROID) -DANDROIDVERSION=$(ANDROIDVERSION) 93 | LDFLAGS += -lm -lGLESv3 -lEGL -landroid -llog -lOpenSLES 94 | LDFLAGS += -shared -uANativeActivity_onCreate 95 | 96 | CC_ARM64:=$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/bin/aarch64-linux-android$(ANDROIDVERSION)-clang 97 | CC_ARM32:=$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/bin/armv7a-linux-androideabi$(ANDROIDVERSION)-clang 98 | CC_x86:=$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/bin/i686-linux-android$(ANDROIDVERSION)-clang 99 | CC_x86_64=$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/bin/x86_64-linux-android$(ANDROIDVERSION)-clang 100 | AAPT:=$(BUILD_TOOLS)/aapt 101 | 102 | # Which binaries to build? Just comment/uncomment these lines: 103 | TARGETS += makecapk/lib/arm64-v8a/lib$(APPNAME).so 104 | #TARGETS += makecapk/lib/armeabi-v7a/lib$(APPNAME).so 105 | #TARGETS += makecapk/lib/x86/lib$(APPNAME).so 106 | #TARGETS += makecapk/lib/x86_64/lib$(APPNAME).so 107 | 108 | CFLAGS_ARM64:=-m64 109 | CFLAGS_ARM32:=-mfloat-abi=softfp -m32 110 | CFLAGS_x86:=-march=i686 -mtune=intel -mssse3 -mfpmath=sse -m32 111 | CFLAGS_x86_64:=-march=x86-64 -msse4.2 -mpopcnt -m64 -mtune=intel 112 | STOREPASS?=password 113 | DNAME:="CN=example.com, OU=ID, O=Example, L=Doe, S=John, C=GB" 114 | KEYSTOREFILE:=my-release-key.keystore 115 | ALIASNAME?=standkey 116 | 117 | keystore : $(KEYSTOREFILE) 118 | 119 | $(KEYSTOREFILE) : 120 | keytool -genkey -v -keystore $(KEYSTOREFILE) -alias $(ALIASNAME) -keyalg RSA -keysize 2048 -validity 10000 -storepass $(STOREPASS) -keypass $(STOREPASS) -dname $(DNAME) 121 | 122 | folders: 123 | mkdir -p makecapk/lib/arm64-v8a 124 | mkdir -p makecapk/lib/armeabi-v7a 125 | mkdir -p makecapk/lib/x86 126 | mkdir -p makecapk/lib/x86_64 127 | 128 | makecapk/lib/arm64-v8a/lib$(APPNAME).so : $(ANDROIDSRCS) 129 | mkdir -p makecapk/lib/arm64-v8a 130 | $(CC_ARM64) $(CFLAGS) $(CFLAGS_ARM64) -o $@ $^ -L$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/sysroot/usr/lib/aarch64-linux-android/$(ANDROIDVERSION) $(LDFLAGS) 131 | 132 | makecapk/lib/armeabi-v7a/lib$(APPNAME).so : $(ANDROIDSRCS) 133 | mkdir -p makecapk/lib/armeabi-v7a 134 | $(CC_ARM32) $(CFLAGS) $(CFLAGS_ARM32) -o $@ $^ -L$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/sysroot/usr/lib/arm-linux-androideabi/$(ANDROIDVERSION) $(LDFLAGS) 135 | 136 | makecapk/lib/x86/lib$(APPNAME).so : $(ANDROIDSRCS) 137 | mkdir -p makecapk/lib/x86 138 | $(CC_x86) $(CFLAGS) $(CFLAGS_x86) -o $@ $^ -L$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/sysroot/usr/lib/i686-linux-android/$(ANDROIDVERSION) $(LDFLAGS) 139 | 140 | makecapk/lib/x86_64/lib$(APPNAME).so : $(ANDROIDSRCS) 141 | mkdir -p makecapk/lib/x86_64 142 | $(CC_x86) $(CFLAGS) $(CFLAGS_x86_64) -o $@ $^ -L$(NDK)/toolchains/llvm/prebuilt/$(OS_NAME)/sysroot/usr/lib/x86_64-linux-android/$(ANDROIDVERSION) $(LDFLAGS) 143 | 144 | #We're really cutting corners. You should probably use resource files.. Replace android:label="@string/app_name" and add a resource file. 145 | #Then do this -S Sources/res on the aapt line. 146 | #For icon support, add -S makecapk/res to the aapt line. also, android:icon="@mipmap/icon" to your application line in the manifest. 147 | #If you want to strip out about 800 bytes of data you can remove the icon and strings. 148 | 149 | #Notes for the past: These lines used to work, but don't seem to anymore. Switched to newer jarsigner. 150 | #(zipalign -c -v 8 makecapk.apk)||true #This seems to not work well. 151 | #jarsigner -verify -verbose -certs makecapk.apk 152 | 153 | 154 | 155 | makecapk.apk : $(TARGETS) $(EXTRA_ASSETS_TRIGGER) AndroidManifest.xml 156 | mkdir -p makecapk/assets 157 | cp -r Sources/assets/* makecapk/assets 158 | rm -rf temp.apk 159 | $(AAPT) package -f -F temp.apk -I $(ANDROIDSDK)/platforms/android-$(ANDROIDVERSION)/android.jar -M AndroidManifest.xml -S Sources/res -A makecapk/assets -v --target-sdk-version $(ANDROIDTARGET) 160 | unzip -o temp.apk -d makecapk 161 | rm -rf makecapk.apk 162 | # We use -4 here for the compression ratio, as it's a good balance of speed and size. -9 will make a slightly smaller executable but takes longer to build 163 | cd makecapk && zip -D4r ../makecapk.apk . && zip -D0r ../makecapk.apk ./resources.arsc ./AndroidManifest.xml 164 | # jarsigner is only necessary when targetting Android < 7.0 165 | #jarsigner -sigalg SHA1withRSA -digestalg SHA1 -verbose -keystore $(KEYSTOREFILE) -storepass $(STOREPASS) makecapk.apk $(ALIASNAME) 166 | rm -rf $(APKFILE) 167 | $(BUILD_TOOLS)/zipalign -v 4 makecapk.apk $(APKFILE) 168 | #Using the apksigner in this way is only required on Android 30+ 169 | $(BUILD_TOOLS)/apksigner sign --key-pass pass:$(STOREPASS) --ks-pass pass:$(STOREPASS) --ks $(KEYSTOREFILE) $(APKFILE) 170 | rm -rf temp.apk 171 | rm -rf makecapk.apk 172 | @ls -l $(APKFILE) 173 | 174 | manifest: AndroidManifest.xml 175 | 176 | AndroidManifest.xml : 177 | rm -rf AndroidManifest.xml 178 | PACKAGENAME=$(PACKAGENAME) \ 179 | ANDROIDVERSION=$(ANDROIDVERSION) \ 180 | ANDROIDTARGET=$(ANDROIDTARGET) \ 181 | APPNAME=$(APPNAME) \ 182 | LABEL=$(LABEL) envsubst '$$ANDROIDTARGET $$ANDROIDVERSION $$APPNAME $$PACKAGENAME $$LABEL' \ 183 | < AndroidManifest.xml.template > AndroidManifest.xml 184 | 185 | 186 | uninstall : 187 | ($(ADB) uninstall $(PACKAGENAME))||true 188 | 189 | push : makecapk.apk 190 | @echo "Installing" $(PACKAGENAME) 191 | $(ADB) install -r $(APKFILE) 192 | 193 | run : push 194 | $(eval ACTIVITYNAME:=$(shell $(AAPT) dump badging $(APKFILE) | grep "launchable-activity" | cut -f 2 -d"'")) 195 | $(ADB) shell am start -n $(PACKAGENAME)/$(ACTIVITYNAME) 196 | 197 | clean : 198 | rm -rf AndroidManifest.xml temp.apk makecapk.apk makecapk $(APKFILE) 199 | 200 | -------------------------------------------------------------------------------- /clay_renderer_cnfg.c: -------------------------------------------------------------------------------- 1 | //#define CNFA_IMPLEMENTATION 2 | #define CNFG_IMPLEMENTATION 3 | #define CNFG3D 4 | #include "CNFGAndroid.h" 5 | #include "CNFG.h" 6 | #include "stdint.h" 7 | #include "string.h" 8 | #include "stdio.h" 9 | #include "stdlib.h" 10 | #ifdef CLAY_OVERFLOW_TRAP 11 | #include "signal.h" 12 | #endif 13 | 14 | #define CLAY_COLOR_TO_CNFG_COLOR(color) (((unsigned char) roundf(color.r) & 0xff) << 24) + (((unsigned char) roundf(color.g) & 0xff) << 16) + (((unsigned char) roundf(color.b) & 0xff) << 8) + ((unsigned char) roundf(color.a) & 0xff) 15 | 16 | typedef struct Image { 17 | int width; 18 | int height; 19 | uint32_t* data; 20 | } Image; 21 | 22 | static inline void CNFG_DrawRectangle(short x0, short y0, short width, short height, uint32_t color) { 23 | CNFGColor( color ); 24 | CNFGTackRectangle( x0, y0, x0 + width, y0 + height ); 25 | } 26 | 27 | static inline Clay_Dimensions CNFG_MeasureText(Clay_String *text, Clay_TextElementConfig *config) { 28 | // Measure string size for Font 29 | Clay_Dimensions textSize = { 0 }; 30 | 31 | //TODO use config->fontSize and the DPI to convert to pixels 32 | float scale = 3; 33 | float widthLine = 0; 34 | 35 | textSize.height = 6 * scale; 36 | 37 | for (int i = 0; i < text->length; ++i) 38 | { 39 | switch (text->chars[i]) 40 | { 41 | case 9: // tab 42 | widthLine += 12 * scale; 43 | break; 44 | case 10: // linefeed 45 | textSize.width = widthLine > textSize.width ? widthLine : textSize.width; 46 | textSize.height += 6 * scale; 47 | widthLine = 0; 48 | break; 49 | default: 50 | widthLine += 3 * scale; 51 | break; 52 | } 53 | } 54 | 55 | textSize.width = widthLine > textSize.width ? widthLine : textSize.width; 56 | 57 | return textSize; 58 | } 59 | 60 | void Clay_CNFG_Initialize(int width, int height, const char *title, unsigned int flags) { 61 | //TODO 62 | } 63 | 64 | void Clay_CNFG_Render(short width, short height, Clay_RenderCommandArray renderCommands) 65 | { 66 | for (int j = 0; j < renderCommands.length; j++) 67 | { 68 | Clay_RenderCommand *renderCommand = Clay_RenderCommandArray_Get(&renderCommands, j); 69 | Clay_BoundingBox boundingBox = renderCommand->boundingBox; 70 | switch (renderCommand->commandType) 71 | { 72 | case CLAY_RENDER_COMMAND_TYPE_TEXT: { 73 | // CNLR uses standard C strings so isn't compatible with cheap slices, we need to clone the string to append null terminator 74 | Clay_String text = renderCommand->text; 75 | char *cloned = (char *)malloc(text.length + 1); 76 | memcpy(cloned, text.chars, text.length); 77 | cloned[text.length] = '\0'; 78 | 79 | //TODO use renderCommand->config.textElementConfig->fontSize and the DPI to convert to pixels 80 | short pixelSize = 3; 81 | 82 | CNFGPenX = boundingBox.x; CNFGPenY = boundingBox.y; 83 | CNFGColor( CLAY_COLOR_TO_CNFG_COLOR(renderCommand->config.textElementConfig->textColor) ); 84 | CNFGDrawText(cloned, pixelSize); 85 | CNFGFlushRender(); 86 | 87 | free(cloned); 88 | break; 89 | } 90 | case CLAY_RENDER_COMMAND_TYPE_IMAGE: { 91 | //TODO generate new image 92 | Image* image = renderCommand->config.imageElementConfig->imageData; 93 | 94 | //Copy the image to resize it 95 | uint32_t *resized = (uint32_t *)malloc(boundingBox.width * boundingBox.height * sizeof(uint32_t)); 96 | for (int x = 0; x < boundingBox.width; x++) { 97 | for (int y = 0; y < boundingBox.height; y++) { 98 | // From https://courses.cs.vt.edu/~masc1044/L17-Rotation/ScalingNN.html 99 | int sourceX = (int) (roundf( x / boundingBox.width * image->width)); 100 | int sourceY = (int) (roundf( y / boundingBox.height * image->height)); 101 | resized[y * (int)(roundf(boundingBox.width)) + x] = image->data[sourceY * image->width + sourceX]; 102 | } 103 | } 104 | 105 | CNFGBlitImage( resized, boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height); 106 | CNFGFlushRender(); 107 | 108 | free(resized); 109 | break; 110 | } 111 | case CLAY_RENDER_COMMAND_TYPE_SCISSOR_START: { 112 | glScissor((int)roundf(boundingBox.x), height - (int)roundf(boundingBox.y + boundingBox.height), (int)roundf(boundingBox.width), (int)roundf(boundingBox.height)); 113 | glEnable(GL_SCISSOR_TEST); 114 | break; 115 | } 116 | case CLAY_RENDER_COMMAND_TYPE_SCISSOR_END: { 117 | glDisable(GL_SCISSOR_TEST); 118 | break; 119 | } 120 | case CLAY_RENDER_COMMAND_TYPE_RECTANGLE: { 121 | Clay_RectangleElementConfig *config = renderCommand->config.rectangleElementConfig; 122 | 123 | CNFG_DrawRectangle(boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height, CLAY_COLOR_TO_CNFG_COLOR(config->color)); 124 | CNFGFlushRender(); 125 | 126 | if (config->cornerRadius.topLeft > 0) { 127 | //TODO 128 | } 129 | break; 130 | } 131 | case CLAY_RENDER_COMMAND_TYPE_BORDER: { 132 | Clay_BorderElementConfig *config = renderCommand->config.borderElementConfig; 133 | // Left border 134 | if (config->left.width > 0) { 135 | CNFG_DrawRectangle((int)roundf(boundingBox.x), (int)roundf(boundingBox.y + config->cornerRadius.topLeft), (int)config->left.width, (int)roundf(boundingBox.height - config->cornerRadius.topLeft - config->cornerRadius.bottomLeft), CLAY_COLOR_TO_CNFG_COLOR(config->left.color)); 136 | } 137 | // Right border 138 | if (config->right.width > 0) { 139 | CNFG_DrawRectangle((int)roundf(boundingBox.x + boundingBox.width - config->right.width), (int)roundf(boundingBox.y + config->cornerRadius.topRight), (int)config->right.width, (int)roundf(boundingBox.height - config->cornerRadius.topRight - config->cornerRadius.bottomRight), CLAY_COLOR_TO_CNFG_COLOR(config->right.color)); 140 | } 141 | // Top border 142 | if (config->top.width > 0) { 143 | CNFG_DrawRectangle((int)roundf(boundingBox.x + config->cornerRadius.topLeft), (int)roundf(boundingBox.y), (int)roundf(boundingBox.width - config->cornerRadius.topLeft - config->cornerRadius.topRight), (int)config->top.width, CLAY_COLOR_TO_CNFG_COLOR(config->top.color)); 144 | } 145 | // Bottom border 146 | if (config->bottom.width > 0) { 147 | CNFG_DrawRectangle((int)roundf(boundingBox.x + config->cornerRadius.bottomLeft), (int)roundf(boundingBox.y + boundingBox.height - config->bottom.width), (int)roundf(boundingBox.width - config->cornerRadius.bottomLeft - config->cornerRadius.bottomRight), (int)config->bottom.width, CLAY_COLOR_TO_CNFG_COLOR(config->bottom.color)); 148 | } 149 | if (config->cornerRadius.topLeft > 0) { 150 | //TODO DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.topLeft), roundf(boundingBox.y + config->cornerRadius.topLeft) }, roundf(config->cornerRadius.topLeft - config->top.width), config->cornerRadius.topLeft, 180, 270, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->top.color)); 151 | } 152 | if (config->cornerRadius.topRight > 0) { 153 | //TODO DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.topRight), roundf(boundingBox.y + config->cornerRadius.topRight) }, roundf(config->cornerRadius.topRight - config->top.width), config->cornerRadius.topRight, 270, 360, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->top.color)); 154 | } 155 | if (config->cornerRadius.bottomLeft > 0) { 156 | //TODO DrawRing((Vector2) { roundf(boundingBox.x + config->cornerRadius.bottomLeft), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomLeft) }, roundf(config->cornerRadius.bottomLeft - config->top.width), config->cornerRadius.bottomLeft, 90, 180, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->bottom.color)); 157 | } 158 | if (config->cornerRadius.bottomRight > 0) { 159 | //TODO DrawRing((Vector2) { roundf(boundingBox.x + boundingBox.width - config->cornerRadius.bottomRight), roundf(boundingBox.y + boundingBox.height - config->cornerRadius.bottomRight) }, roundf(config->cornerRadius.bottomRight - config->bottom.width), config->cornerRadius.bottomRight, 0.1, 90, 10, CLAY_COLOR_TO_RAYLIB_COLOR(config->bottom.color)); 160 | } 161 | 162 | CNFGFlushRender(); 163 | break; 164 | } 165 | case CLAY_RENDER_COMMAND_TYPE_CUSTOM: { 166 | //TODO 167 | /* 168 | CustomLayoutElement *customElement = (CustomLayoutElement *)renderCommand->config.customElementConfig->customData; 169 | if (!customElement) continue; 170 | switch (customElement->type) { 171 | case CUSTOM_LAYOUT_ELEMENT_TYPE_3D_MODEL: { 172 | Clay_BoundingBox rootBox = renderCommands.internalArray[0].boundingBox; 173 | float scaleValue = CLAY__MIN(CLAY__MIN(1, 768 / rootBox.height) * CLAY__MAX(1, rootBox.width / 1024), 1.5f); 174 | Ray positionRay = GetScreenToWorldPointWithZDistance((Vector2) { renderCommand->boundingBox.x + renderCommand->boundingBox.width / 2, renderCommand->boundingBox.y + (renderCommand->boundingBox.height / 2) + 20 }, Raylib_camera, (int)roundf(rootBox.width), (int)roundf(rootBox.height), 140); 175 | BeginMode3D(Raylib_camera); 176 | DrawModel(customElement->model.model, positionRay.position, customElement->model.scale * scaleValue, WHITE); // Draw 3d model with texture 177 | EndMode3D(); 178 | break; 179 | } 180 | default: break; 181 | } 182 | */ 183 | break; 184 | } 185 | default: { 186 | printf("Error: unhandled render command."); 187 | #ifdef CLAY_OVERFLOW_TRAP 188 | raise(SIGTRAP); 189 | #endif 190 | } 191 | } 192 | } 193 | } 194 | 195 | -------------------------------------------------------------------------------- /android_usb_devices.c: -------------------------------------------------------------------------------- 1 | //Copyright 2020 <>< Charles Lohr, You may use this file and library freely under the MIT/x11, NewBSD or ColorChord Licenses. 2 | 3 | #include "android_usb_devices.h" 4 | #include "CNFG.h" 5 | #include "os_generic.h" 6 | 7 | double dTimeOfUSBFail; 8 | double dTimeOfLastAsk; 9 | jobject deviceConnection = 0; 10 | int deviceConnectionFD = 0; 11 | extern struct android_app * gapp; 12 | 13 | void DisconnectUSB() 14 | { 15 | deviceConnectionFD = 0; 16 | dTimeOfUSBFail = OGGetAbsoluteTime(); 17 | } 18 | 19 | int RequestPermissionOrGetConnectionFD( char * ats, uint16_t vid, uint16_t pid ) 20 | { 21 | //Don't permit 22 | if( OGGetAbsoluteTime() - dTimeOfUSBFail < 1 ) 23 | { 24 | ats+=sprintf(ats, "Comms failed. Waiting to reconnect." ); 25 | return -1; 26 | } 27 | 28 | struct android_app* app = gapp; 29 | const struct JNINativeInterface * env = 0; 30 | const struct JNINativeInterface ** envptr = &env; 31 | const struct JNIInvokeInterface ** jniiptr = app->activity->vm; 32 | const struct JNIInvokeInterface * jnii = *jniiptr; 33 | jnii->AttachCurrentThread( jniiptr, &envptr, NULL); 34 | env = (*envptr); 35 | 36 | // Retrieves NativeActivity. 37 | jobject lNativeActivity = gapp->activity->clazz; 38 | 39 | //https://stackoverflow.com/questions/13280581/using-android-to-communicate-with-a-usb-hid-device 40 | 41 | //UsbManager manager = (UsbManager)getSystemService(Context.USB_SERVICE); 42 | jclass ClassContext = env->FindClass( envptr, "android/content/Context" ); 43 | jfieldID lid_USB_SERVICE = env->GetStaticFieldID( envptr, ClassContext, "USB_SERVICE", "Ljava/lang/String;" ); 44 | jobject USB_SERVICE = env->GetStaticObjectField( envptr, ClassContext, lid_USB_SERVICE ); 45 | 46 | jmethodID MethodgetSystemService = env->GetMethodID( envptr, ClassContext, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;" ); 47 | jobject manager = env->CallObjectMethod( envptr, lNativeActivity, MethodgetSystemService, USB_SERVICE); 48 | //Actually returns an android/hardware/usb/UsbManager 49 | jclass ClassUsbManager = env->FindClass( envptr, "android/hardware/usb/UsbManager" ); 50 | 51 | //HashMap deviceList = mManager.getDeviceList(); 52 | jmethodID MethodgetDeviceList = env->GetMethodID( envptr, ClassUsbManager, "getDeviceList", "()Ljava/util/HashMap;" ); 53 | jobject deviceList = env->CallObjectMethod( envptr, manager, MethodgetDeviceList ); 54 | 55 | //Iterator deviceIterator = deviceList.values().iterator(); 56 | jclass ClassHashMap = env->FindClass( envptr, "java/util/HashMap" ); 57 | jmethodID Methodvalues = env->GetMethodID( envptr, ClassHashMap, "values", "()Ljava/util/Collection;" ); 58 | jobject deviceListCollection = env->CallObjectMethod( envptr, deviceList, Methodvalues ); 59 | jclass ClassCollection = env->FindClass( envptr, "java/util/Collection" ); 60 | jmethodID Methoditerator = env->GetMethodID( envptr, ClassCollection, "iterator", "()Ljava/util/Iterator;" ); 61 | jobject deviceListIterator = env->CallObjectMethod( envptr, deviceListCollection, Methoditerator ); 62 | jclass ClassIterator = env->FindClass( envptr, "java/util/Iterator" ); 63 | 64 | //while (deviceIterator.hasNext()) 65 | jmethodID MethodhasNext = env->GetMethodID( envptr, ClassIterator, "hasNext", "()Z" ); 66 | jboolean bHasNext = env->CallBooleanMethod( envptr, deviceListIterator, MethodhasNext ); 67 | 68 | ats+=sprintf(ats, "Has Devices: %d\n", bHasNext ); 69 | 70 | jmethodID Methodnext = env->GetMethodID( envptr, ClassIterator, "next", "()Ljava/lang/Object;" ); 71 | 72 | jclass ClassUsbDevice = env->FindClass( envptr, "android/hardware/usb/UsbDevice" ); 73 | jclass ClassUsbInterface = env->FindClass( envptr, "android/hardware/usb/UsbInterface" ); 74 | jclass ClassUsbEndpoint = env->FindClass( envptr, "android/hardware/usb/UsbEndpoint" ); 75 | jclass ClassUsbDeviceConnection = env->FindClass( envptr, "android/hardware/usb/UsbDeviceConnection" ); 76 | jmethodID MethodgetDeviceName = env->GetMethodID( envptr, ClassUsbDevice, "getDeviceName", "()Ljava/lang/String;" ); 77 | jmethodID MethodgetVendorId = env->GetMethodID( envptr, ClassUsbDevice, "getVendorId", "()I" ); 78 | jmethodID MethodgetProductId = env->GetMethodID( envptr, ClassUsbDevice, "getProductId", "()I" ); 79 | jmethodID MethodgetInterfaceCount = env->GetMethodID( envptr, ClassUsbDevice, "getInterfaceCount", "()I" ); 80 | jmethodID MethodgetInterface = env->GetMethodID( envptr, ClassUsbDevice, "getInterface", "(I)Landroid/hardware/usb/UsbInterface;" ); 81 | 82 | jmethodID MethodgetEndpointCount = env->GetMethodID( envptr, ClassUsbInterface, "getEndpointCount", "()I" ); 83 | jmethodID MethodgetEndpoint = env->GetMethodID( envptr, ClassUsbInterface, "getEndpoint", "(I)Landroid/hardware/usb/UsbEndpoint;" ); 84 | 85 | jmethodID MethodgetAddress = env->GetMethodID( envptr, ClassUsbEndpoint, "getAddress", "()I" ); 86 | jmethodID MethodgetMaxPacketSize = env->GetMethodID( envptr, ClassUsbEndpoint, "getMaxPacketSize", "()I" ); 87 | 88 | jobject matchingDevice = 0; 89 | jobject matchingInterface = 0; 90 | 91 | while( bHasNext ) 92 | { 93 | // UsbDevice device = deviceIterator.next(); 94 | // Log.i(TAG,"Model: " + device.getDeviceName()); 95 | jobject device = env->CallObjectMethod( envptr, deviceListIterator, Methodnext ); 96 | uint16_t vendorId = env->CallIntMethod( envptr, device, MethodgetVendorId ); 97 | uint16_t productId = env->CallIntMethod( envptr, device, MethodgetProductId ); 98 | int ifaceCount = env->CallIntMethod( envptr, device, MethodgetInterfaceCount ); 99 | const char *strdevname = env->GetStringUTFChars(envptr, env->CallObjectMethod( envptr, device, MethodgetDeviceName ), 0); 100 | ats+=sprintf(ats, "%s,%04x:%04x(%d)\n", strdevname, 101 | vendorId, 102 | productId, ifaceCount ); 103 | 104 | if( vendorId == vid && productId == pid ) 105 | { 106 | if( ifaceCount ) 107 | { 108 | matchingDevice = device; 109 | matchingInterface = env->CallObjectMethod( envptr, device, MethodgetInterface, 0 ); 110 | } 111 | } 112 | 113 | bHasNext = env->CallBooleanMethod( envptr, deviceListIterator, MethodhasNext ); 114 | } 115 | 116 | jobject matchingEp = 0; 117 | 118 | if( matchingInterface ) 119 | { 120 | //matchingInterface is of type android/hardware/usb/UsbInterface 121 | int epCount = env->CallIntMethod( envptr, matchingInterface, MethodgetEndpointCount ); 122 | ats+=sprintf(ats, "Found device %d eps\n", epCount ); 123 | int i; 124 | for( i = 0; i < epCount; i++ ) 125 | { 126 | jobject endpoint = env->CallObjectMethod( envptr, matchingInterface, MethodgetEndpoint, i ); 127 | jint epnum = env->CallIntMethod( envptr, endpoint, MethodgetAddress ); 128 | jint mps = env->CallIntMethod( envptr, endpoint, MethodgetMaxPacketSize ); 129 | if( epnum == 0x02 ) matchingEp = endpoint; 130 | ats+=sprintf(ats, "%p: %02x: MPS: %d (%c)\n", endpoint, epnum, mps, (matchingEp == endpoint)?'*':' ' ); 131 | } 132 | } 133 | 134 | jmethodID MethodopenDevice = env->GetMethodID( envptr, ClassUsbManager, "openDevice", "(Landroid/hardware/usb/UsbDevice;)Landroid/hardware/usb/UsbDeviceConnection;" ); 135 | jmethodID MethodrequestPermission = env->GetMethodID( envptr, ClassUsbManager, "requestPermission", "(Landroid/hardware/usb/UsbDevice;Landroid/app/PendingIntent;)V" ); 136 | jmethodID MethodhasPermission = env->GetMethodID( envptr, ClassUsbManager, "hasPermission", "(Landroid/hardware/usb/UsbDevice;)Z" ); 137 | jmethodID MethodclaimInterface = env->GetMethodID( envptr, ClassUsbDeviceConnection, "claimInterface", "(Landroid/hardware/usb/UsbInterface;Z)Z" ); 138 | jmethodID MethodsetInterface = env->GetMethodID( envptr, ClassUsbDeviceConnection, "setInterface", "(Landroid/hardware/usb/UsbInterface;)Z" ); 139 | jmethodID MethodgetFileDescriptor = env->GetMethodID( envptr, ClassUsbDeviceConnection, "getFileDescriptor", "()I" ); 140 | //jmethodID MethodbulkTransfer = env->GetMethodID( envptr, ClassUsbDeviceConnection, "bulkTransfer", "(Landroid/hardware/usb/UsbEndpoint;[BII)I" ); 141 | 142 | //see https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/hardware/usb/UsbDeviceConnection.java 143 | //Calls: native_bulk_request -> android_hardware_UsbDeviceConnection_bulk_request -> usb_device_bulk_transfer 144 | // UsbEndpoint endpoint, byte[] buffer, int length, int timeout 145 | //bulkTransfer(UsbEndpoint endpoint, byte[] buffer, int length, int timeout) 146 | 147 | //UsbDeviceConnection bulkTransfer 148 | 149 | if( matchingEp && matchingDevice ) 150 | { 151 | //UsbDeviceConnection deviceConnection = manager.openDevice( device ) 152 | deviceConnection = env->CallObjectMethod( envptr, manager, MethodopenDevice, matchingDevice ); 153 | jint epnum = env->CallIntMethod( envptr, matchingEp, MethodgetAddress ); 154 | 155 | if( !deviceConnection ) 156 | { 157 | // hasPermission(UsbDevice device) 158 | 159 | if( OGGetAbsoluteTime() - dTimeOfLastAsk < 5 ) 160 | { 161 | ats+=sprintf(ats, "Asked for permission. Waiting to ask again." ); 162 | } 163 | else if( env->CallBooleanMethod( envptr, manager, MethodhasPermission, matchingDevice ) ) 164 | { 165 | ats+=sprintf(ats, "Has permission - disconnected?" ); 166 | } 167 | else 168 | { 169 | //android.app.PendingIntent currently setting to 0 (null) seems not to cause crashes, but does force lock screen to happen. 170 | //Because the screen locks we need to do a much more complicated operation, generating a PendingIntent. See Below. 171 | // env->CallVoidMethod( envptr, manager, MethodrequestPermission, matchingDevice, 0 ); 172 | 173 | //This part mimiced off of: 174 | //https://www.programcreek.com/java-api-examples/?class=android.hardware.usb.UsbManager&method=requestPermission 175 | // manager.requestPermission(device, PendingIntent.getBroadcast(context, 0, new Intent(MainActivity.ACTION_USB_PERMISSION), 0)); 176 | jclass ClassPendingIntent = env->FindClass( envptr, "android/app/PendingIntent" ); 177 | jclass ClassIntent = env->FindClass(envptr, "android/content/Intent"); 178 | jmethodID newIntent = env->GetMethodID(envptr, ClassIntent, "", "(Ljava/lang/String;)V"); 179 | jstring ACTION_USB_PERMISSION = env->NewStringUTF( envptr, "com.android.recipes.USB_PERMISSION" ); 180 | jobject intentObject = env->NewObject(envptr, ClassIntent, newIntent, ACTION_USB_PERMISSION); 181 | 182 | jmethodID MethodgetBroadcast = env->GetStaticMethodID( envptr, ClassPendingIntent, "getBroadcast", 183 | "(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;" ); 184 | jobject pi = env->CallStaticObjectMethod( envptr, ClassPendingIntent, MethodgetBroadcast, lNativeActivity, 0, intentObject, 0 ); 185 | 186 | //This actually requests permission. 187 | env->CallVoidMethod( envptr, manager, MethodrequestPermission, matchingDevice, pi ); 188 | dTimeOfLastAsk = OGGetAbsoluteTime(); 189 | } 190 | } 191 | else 192 | { 193 | //Because we want to read and write to an interrupt endpoint, we need to claim the interface - it seems setting interfaces is insufficient here. 194 | jboolean claimOk = env->CallBooleanMethod( envptr, deviceConnection, MethodclaimInterface, matchingInterface, 1 ); 195 | //jboolean claimOk = env->CallBooleanMethod( envptr, deviceConnection, MethodsetInterface, matchingInterface ); 196 | //jboolean claimOk = 1; 197 | if( claimOk ) 198 | { 199 | deviceConnectionFD = env->CallIntMethod( envptr, deviceConnection, MethodgetFileDescriptor ); 200 | } 201 | 202 | ats+=sprintf(ats, "DC: %p; Claim: %d; FD: %d\n", deviceConnection, claimOk, deviceConnectionFD ); 203 | } 204 | 205 | } 206 | 207 | jnii->DetachCurrentThread( jniiptr ); 208 | return (!deviceConnectionFD)?-5:0; 209 | } 210 | 211 | -------------------------------------------------------------------------------- /android_native_app_glue.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | #ifndef _ANDROID_NATIVE_APP_GLUE_H 19 | #define _ANDROID_NATIVE_APP_GLUE_H 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | /** 34 | * The native activity interface provided by 35 | * is based on a set of application-provided callbacks that will be called 36 | * by the Activity's main thread when certain events occur. 37 | * 38 | * This means that each one of this callbacks _should_ _not_ block, or they 39 | * risk having the system force-close the application. This programming 40 | * model is direct, lightweight, but constraining. 41 | * 42 | * The 'android_native_app_glue' static library is used to provide a different 43 | * execution model where the application can implement its own main event 44 | * loop in a different thread instead. Here's how it works: 45 | * 46 | * 1/ The application must provide a function named "android_main()" that 47 | * will be called when the activity is created, in a new thread that is 48 | * distinct from the activity's main thread. 49 | * 50 | * 2/ android_main() receives a pointer to a valid "android_app" structure 51 | * that contains references to other important objects, e.g. the 52 | * ANativeActivity object instance the application is running in. 53 | * 54 | * 3/ the "android_app" object holds an ALooper instance that already 55 | * listens to two important things: 56 | * 57 | * - activity lifecycle events (e.g. "pause", "resume"). See APP_CMD_XXX 58 | * declarations below. 59 | * 60 | * - input events coming from the AInputQueue attached to the activity. 61 | * 62 | * Each of these correspond to an ALooper identifier returned by 63 | * ALooper_pollOnce with values of LOOPER_ID_MAIN and LOOPER_ID_INPUT, 64 | * respectively. 65 | * 66 | * Your application can use the same ALooper to listen to additional 67 | * file-descriptors. They can either be callback based, or with return 68 | * identifiers starting with LOOPER_ID_USER. 69 | * 70 | * 4/ Whenever you receive a LOOPER_ID_MAIN or LOOPER_ID_INPUT event, 71 | * the returned data will point to an android_poll_source structure. You 72 | * can call the process() function on it, and fill in android_app->onAppCmd 73 | * and android_app->onInputEvent to be called for your own processing 74 | * of the event. 75 | * 76 | * Alternatively, you can call the low-level functions to read and process 77 | * the data directly... look at the process_cmd() and process_input() 78 | * implementations in the glue to see how to do this. 79 | * 80 | * See the sample named "native-activity" that comes with the NDK with a 81 | * full usage example. Also look at the JavaDoc of NativeActivity. 82 | */ 83 | 84 | struct android_app; 85 | 86 | /** 87 | * Data associated with an ALooper fd that will be returned as the "outData" 88 | * when that source has data ready. 89 | */ 90 | struct android_poll_source { 91 | // The identifier of this source. May be LOOPER_ID_MAIN or 92 | // LOOPER_ID_INPUT. 93 | int32_t id; 94 | 95 | // The android_app this ident is associated with. 96 | struct android_app* app; 97 | 98 | // Function to call to perform the standard processing of data from 99 | // this source. 100 | void (*process)(struct android_app* app, struct android_poll_source* source); 101 | }; 102 | 103 | /** 104 | * This is the interface for the standard glue code of a threaded 105 | * application. In this model, the application's code is running 106 | * in its own thread separate from the main thread of the process. 107 | * It is not required that this thread be associated with the Java 108 | * VM, although it will need to be in order to make JNI calls any 109 | * Java objects. 110 | */ 111 | struct android_app { 112 | // The application can place a pointer to its own state object 113 | // here if it likes. 114 | void* userData; 115 | 116 | // Fill this in with the function to process main app commands (APP_CMD_*) 117 | void (*onAppCmd)(struct android_app* app, int32_t cmd); 118 | 119 | // Fill this in with the function to process input events. At this point 120 | // the event has already been pre-dispatched, and it will be finished upon 121 | // return. Return 1 if you have handled the event, 0 for any default 122 | // dispatching. 123 | int32_t (*onInputEvent)(struct android_app* app, AInputEvent* event); 124 | 125 | // The ANativeActivity object instance that this app is running in. 126 | ANativeActivity* activity; 127 | 128 | // The current configuration the app is running in. 129 | AConfiguration* config; 130 | 131 | // This is the last instance's saved state, as provided at creation time. 132 | // It is NULL if there was no state. You can use this as you need; the 133 | // memory will remain around until you call android_app_exec_cmd() for 134 | // APP_CMD_RESUME, at which point it will be freed and savedState set to NULL. 135 | // These variables should only be changed when processing a APP_CMD_SAVE_STATE, 136 | // at which point they will be initialized to NULL and you can malloc your 137 | // state and place the information here. In that case the memory will be 138 | // freed for you later. 139 | void* savedState; 140 | size_t savedStateSize; 141 | 142 | // The ALooper associated with the app's thread. 143 | ALooper* looper; 144 | 145 | 146 | // The ALooper associated with the main thread. 147 | ALooper* looperui; 148 | 149 | // When non-NULL, this is the input queue from which the app will 150 | // receive user input events. 151 | AInputQueue* inputQueue; 152 | 153 | // When non-NULL, this is the window surface that the app can draw in. 154 | ANativeWindow* window; 155 | 156 | // Current content rectangle of the window; this is the area where the 157 | // window's content should be placed to be seen by the user. 158 | ARect contentRect; 159 | 160 | // Current state of the app's activity. May be either APP_CMD_START, 161 | // APP_CMD_RESUME, APP_CMD_PAUSE, or APP_CMD_STOP; see below. 162 | int activityState; 163 | 164 | // This is non-zero when the application's NativeActivity is being 165 | // destroyed and waiting for the app thread to complete. 166 | int destroyRequested; 167 | 168 | // ------------------------------------------------- 169 | // Below are "private" implementation of the glue code. 170 | 171 | pthread_mutex_t mutex; 172 | pthread_cond_t cond; 173 | 174 | int msgread; 175 | int msgwrite; 176 | 177 | int uimsgread; 178 | int uimsgwrite; 179 | 180 | pthread_t thread; 181 | 182 | struct android_poll_source cmdPollSource; 183 | struct android_poll_source inputPollSource; 184 | 185 | int running; 186 | int stateSaved; 187 | int destroyed; 188 | int redrawNeeded; 189 | AInputQueue* pendingInputQueue; 190 | ANativeWindow* pendingWindow; 191 | ARect pendingContentRect; 192 | }; 193 | 194 | enum { 195 | /** 196 | * Looper data ID of commands coming from the app's main thread, which 197 | * is returned as an identifier from ALooper_pollOnce(). The data for this 198 | * identifier is a pointer to an android_poll_source structure. 199 | * These can be retrieved and processed with android_app_read_cmd() 200 | * and android_app_exec_cmd(). 201 | */ 202 | LOOPER_ID_MAIN = 1, 203 | 204 | /** 205 | * Looper data ID of events coming from the AInputQueue of the 206 | * application's window, which is returned as an identifier from 207 | * ALooper_pollOnce(). The data for this identifier is a pointer to an 208 | * android_poll_source structure. These can be read via the inputQueue 209 | * object of android_app. 210 | */ 211 | LOOPER_ID_INPUT = 2, 212 | 213 | /** 214 | * Start of main ALooper identifiers. 215 | */ 216 | LOOPER_ID_MAIN_THREAD = 3, 217 | 218 | /** 219 | * Start of user-defined ALooper identifiers. 220 | */ 221 | LOOPER_ID_USER = 4, 222 | }; 223 | 224 | enum { 225 | /** 226 | * Command from main thread: the AInputQueue has changed. Upon processing 227 | * this command, android_app->inputQueue will be updated to the new queue 228 | * (or NULL). 229 | */ 230 | APP_CMD_INPUT_CHANGED, 231 | 232 | /** 233 | * Command from main thread: a new ANativeWindow is ready for use. Upon 234 | * receiving this command, android_app->window will contain the new window 235 | * surface. 236 | */ 237 | APP_CMD_INIT_WINDOW, 238 | 239 | /** 240 | * Command from main thread: the existing ANativeWindow needs to be 241 | * terminated. Upon receiving this command, android_app->window still 242 | * contains the existing window; after calling android_app_exec_cmd 243 | * it will be set to NULL. 244 | */ 245 | APP_CMD_TERM_WINDOW, 246 | 247 | /** 248 | * Command from main thread: the current ANativeWindow has been resized. 249 | * Please redraw with its new size. 250 | */ 251 | APP_CMD_WINDOW_RESIZED, 252 | 253 | /** 254 | * Command from main thread: the system needs that the current ANativeWindow 255 | * be redrawn. You should redraw the window before handing this to 256 | * android_app_exec_cmd() in order to avoid transient drawing glitches. 257 | */ 258 | APP_CMD_WINDOW_REDRAW_NEEDED, 259 | 260 | /** 261 | * Command from main thread: the content area of the window has changed, 262 | * such as from the soft input window being shown or hidden. You can 263 | * find the new content rect in android_app::contentRect. 264 | */ 265 | APP_CMD_CONTENT_RECT_CHANGED, 266 | 267 | /** 268 | * Command from main thread: the app's activity window has gained 269 | * input focus. 270 | */ 271 | APP_CMD_GAINED_FOCUS, 272 | 273 | /** 274 | * Command from main thread: the app's activity window has lost 275 | * input focus. 276 | */ 277 | APP_CMD_LOST_FOCUS, 278 | 279 | /** 280 | * Command from main thread: the current device configuration has changed. 281 | */ 282 | APP_CMD_CONFIG_CHANGED, 283 | 284 | /** 285 | * Command from main thread: the system is running low on memory. 286 | * Try to reduce your memory use. 287 | */ 288 | APP_CMD_LOW_MEMORY, 289 | 290 | /** 291 | * Command from main thread: the app's activity has been started. 292 | */ 293 | APP_CMD_START, 294 | 295 | /** 296 | * Command from main thread: the app's activity has been resumed. 297 | */ 298 | APP_CMD_RESUME, 299 | 300 | /** 301 | * Command from main thread: the app should generate a new saved state 302 | * for itself, to restore from later if needed. If you have saved state, 303 | * allocate it with malloc and place it in android_app.savedState with 304 | * the size in android_app.savedStateSize. The will be freed for you 305 | * later. 306 | */ 307 | APP_CMD_SAVE_STATE, 308 | 309 | /** 310 | * Command from main thread: the app's activity has been paused. 311 | */ 312 | APP_CMD_PAUSE, 313 | 314 | /** 315 | * Command from main thread: the app's activity has been stopped. 316 | */ 317 | APP_CMD_STOP, 318 | 319 | /** 320 | * Command from main thread: the app's activity is being destroyed, 321 | * and waiting for the app thread to clean up and exit before proceeding. 322 | */ 323 | APP_CMD_DESTROY, 324 | 325 | /** 326 | * Custom command to execute something from an event queue 327 | */ 328 | APP_CMD_CUSTOM_EVENT, 329 | }; 330 | 331 | /** 332 | * Call when ALooper_pollAll() returns LOOPER_ID_MAIN, reading the next 333 | * app command message. 334 | */ 335 | int8_t android_app_read_cmd(struct android_app* android_app); 336 | 337 | /** 338 | * Call with the command returned by android_app_read_cmd() to do the 339 | * initial pre-processing of the given command. You can perform your own 340 | * actions for the command after calling this function. 341 | */ 342 | void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd); 343 | 344 | /** 345 | * Call with the command returned by android_app_read_cmd() to do the 346 | * final post-processing of the given command. You must have done your own 347 | * actions for the command before calling this function. 348 | */ 349 | void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd); 350 | 351 | /** 352 | * Dummy function that used to be used to prevent the linker from stripping app 353 | * glue code. No longer necessary, since __attribute__((visibility("default"))) 354 | * does this for us. 355 | */ 356 | __attribute__(( 357 | deprecated("Calls to app_dummy are no longer necessary. See " 358 | "https://github.com/android-ndk/ndk/issues/381."))) void 359 | app_dummy(); 360 | 361 | /** 362 | * This is the function that application code must implement, representing 363 | * the main entry to the app. 364 | */ 365 | extern void android_main(struct android_app* app); 366 | 367 | /** 368 | * Mechanism to run code on main UI thread. 369 | */ 370 | void RunCallbackOnUIThread( void (*callback)(void *), void * opaque ); 371 | 372 | #ifdef __cplusplus 373 | } 374 | #endif 375 | 376 | #endif /* _ANDROID_NATIVE_APP_GLUE_H */ 377 | -------------------------------------------------------------------------------- /webview_native_activity.h: -------------------------------------------------------------------------------- 1 | #ifndef _WEBVIEW_NATIVE_ACTIVITY 2 | #define _WEBVIEW_NATIVE_ACTIVITY 3 | 4 | #include 5 | 6 | extern volatile jobject g_objRootView; 7 | 8 | typedef struct 9 | { 10 | jobject WebViewObject; 11 | jobjectArray MessageChannels; 12 | jobject BackingBitmap; 13 | jobject BackingCanvas; 14 | int updated_canvas; 15 | int w, h; 16 | } WebViewNativeActivityObject; 17 | 18 | // Must be called from main thread 19 | 20 | // initial_url = "about:blank" for a java-script only page. Can also be file:///android_asset/test.html. 21 | // Loading from "about:blank" will make the page ready almost immediately, otherwise it's about 50ms to load. 22 | // useLooperForWebMessages is required, and must be a global jobject of your preferred looper to handle webmessages. 23 | void WebViewCreate( WebViewNativeActivityObject * w, const char * initial_url, jobject useLooperForWebMessages, int pw, int ph ); 24 | void WebViewExecuteJavascript( WebViewNativeActivityObject * obj, const char * js ); 25 | 26 | // Note: Do not initialize until page reports as 100% loaded, with WebViewGetProgress. 27 | void WebViewPostMessage( WebViewNativeActivityObject * obj, const char * mesg, int initial ); 28 | void WebViewRequestRenderToCanvas( WebViewNativeActivityObject * obj ); 29 | int WebViewGetProgress( WebViewNativeActivityObject * obj ); 30 | char * WebViewGetLastWindowTitle( WebViewNativeActivityObject * obj ); 31 | 32 | // Can be called from any thread. 33 | void WebViewNativeGetPixels( WebViewNativeActivityObject * obj, uint32_t * pixel_data, int w, int h ); 34 | 35 | #ifdef WEBVIEW_NATIVE_ACTIVITY_IMPLEMENTATION 36 | 37 | volatile jobject g_objRootView; 38 | 39 | void WebViewCreate( WebViewNativeActivityObject * w, const char * initial_url, jobject useLooperForWebMessages, int pw, int ph ) 40 | { 41 | const struct JNINativeInterface * env = 0; 42 | const struct JNINativeInterface ** envptr = &env; 43 | const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; 44 | jobject clazz = gapp->activity->clazz; 45 | const struct JNIInvokeInterface * jnii = *jniiptr; 46 | 47 | jnii->AttachCurrentThread( jniiptr, &envptr, NULL); 48 | env = (*envptr); 49 | 50 | if( g_objRootView == 0 ) 51 | { 52 | jclass ViewClass = env->FindClass(envptr, "android/widget/LinearLayout"); 53 | jmethodID ViewConstructor = env->GetMethodID(envptr, ViewClass, "", "(Landroid/content/Context;)V"); 54 | jclass activityClass = env->FindClass(envptr, "android/app/Activity"); 55 | jmethodID activityGetContextMethod = env->GetMethodID(envptr, activityClass, "getApplicationContext", "()Landroid/content/Context;"); 56 | jobject contextObject = env->CallObjectMethod(envptr, clazz, activityGetContextMethod); 57 | jobject jv = env->NewObject(envptr, ViewClass, ViewConstructor, contextObject ); 58 | g_objRootView = env->NewGlobalRef(envptr, jv); 59 | 60 | jclass clszz = env->GetObjectClass(envptr,clazz); 61 | jmethodID setContentViewMethod = env->GetMethodID(envptr, clszz, "setContentView", "(Landroid/view/View;)V"); 62 | env->CallVoidMethod(envptr,clazz, setContentViewMethod, g_objRootView ); 63 | } 64 | 65 | jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView"); 66 | jclass activityClass = env->FindClass(envptr, "android/app/Activity"); 67 | jmethodID activityGetContextMethod = env->GetMethodID(envptr, activityClass, "getApplicationContext", "()Landroid/content/Context;"); 68 | jobject contextObject = env->CallObjectMethod(envptr, clazz, activityGetContextMethod); 69 | 70 | jmethodID WebViewConstructor = env->GetMethodID(envptr, WebViewClass, "", "(Landroid/content/Context;)V"); 71 | jobject wvObj = env->NewObject(envptr, WebViewClass, WebViewConstructor, contextObject ); 72 | 73 | // Unknown reason why - if you don't first load about:blank, it sometimes doesn't render right? 74 | // Even more annoying - you can't pre-use loadUrl if you want to use message channels. 75 | jmethodID WebViewLoadBaseURLMethod = env->GetMethodID(envptr, WebViewClass, "loadDataWithBaseURL", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); 76 | jstring strul = env->NewStringUTF( envptr, "http://example.com" ); 77 | jstring strdata = env->NewStringUTF( envptr, "not-yet-loaded" ); 78 | jstring strmime = env->NewStringUTF( envptr, "text/html" ); 79 | jstring strencoding = env->NewStringUTF( envptr, "utf8" ); 80 | jstring strhistoryurl = env->NewStringUTF( envptr, "" ); 81 | env->CallVoidMethod(envptr, wvObj, WebViewLoadBaseURLMethod, strul, strdata, strmime, strencoding, strhistoryurl ); 82 | env->DeleteLocalRef( envptr, strul ); 83 | env->DeleteLocalRef( envptr, strdata ); 84 | env->DeleteLocalRef( envptr, strmime ); 85 | env->DeleteLocalRef( envptr, strencoding ); 86 | env->DeleteLocalRef( envptr, strhistoryurl ); 87 | 88 | // You have to switch to this to be able to run javascript code. 89 | jmethodID LoadURLMethod = env->GetMethodID(envptr, WebViewClass, "loadUrl", "(Ljava/lang/String;)V"); 90 | jstring strjs = env->NewStringUTF( envptr, initial_url ); 91 | env->CallVoidMethod(envptr, wvObj, LoadURLMethod, strjs ); 92 | env->DeleteLocalRef( envptr, strjs ); 93 | 94 | jmethodID WebViewGetSettingMethod = env->GetMethodID(envptr, WebViewClass, "getSettings", "()Landroid/webkit/WebSettings;"); 95 | jobject websettings = env->CallObjectMethod(envptr, wvObj, WebViewGetSettingMethod ); 96 | jclass WebSettingsClass = env->FindClass(envptr, "android/webkit/WebSettings"); 97 | jmethodID setJavaScriptEnabledMethod = env->GetMethodID(envptr, WebSettingsClass, "setJavaScriptEnabled", "(Z)V"); 98 | env->CallVoidMethod( envptr, websettings, setJavaScriptEnabledMethod, true ); 99 | env->DeleteLocalRef( envptr, websettings ); 100 | 101 | jmethodID setMeasuredDimensionMethodID = env->GetMethodID(envptr, WebViewClass, "setMeasuredDimension", "(II)V"); 102 | env->CallVoidMethod(envptr, wvObj, setMeasuredDimensionMethodID, pw, ph ); 103 | 104 | jclass ViewClass = env->FindClass(envptr, "android/widget/LinearLayout"); 105 | jmethodID addViewMethod = env->GetMethodID(envptr, ViewClass, "addView", "(Landroid/view/View;)V"); 106 | env->CallVoidMethod( envptr, g_objRootView, addViewMethod, wvObj ); 107 | 108 | jclass WebMessagePortClass = env->FindClass(envptr, "android/webkit/WebMessagePort" ); 109 | jmethodID createWebMessageChannelMethod = env->GetMethodID(envptr, WebViewClass, "createWebMessageChannel", "()[Landroid/webkit/WebMessagePort;"); 110 | jobjectArray messageChannels = env->CallObjectMethod( envptr, wvObj, createWebMessageChannelMethod ); 111 | jobject mc0 = env->GetObjectArrayElement(envptr, messageChannels, 0); // MC1 is handed over to javascript. 112 | 113 | jclass HandlerClassType = env->FindClass(envptr, "android/os/Handler" ); 114 | jmethodID HandlerObjectConstructor = env->GetMethodID(envptr, HandlerClassType, "", "(Landroid/os/Looper;)V"); 115 | jobject handlerObject = env->NewObject( envptr, HandlerClassType, HandlerObjectConstructor, useLooperForWebMessages ); 116 | handlerObject = env->NewGlobalRef(envptr, handlerObject); 117 | jmethodID setWebMessageCallbackMethod = env->GetMethodID( envptr, WebMessagePortClass, "setWebMessageCallback", "(Landroid/webkit/WebMessagePort$WebMessageCallback;Landroid/os/Handler;)V" ); 118 | 119 | // Only can receive messages on MC0 120 | env->CallVoidMethod( envptr, mc0, setWebMessageCallbackMethod, 0, handlerObject ); 121 | 122 | // Generate backing bitmap and canvas. 123 | jclass CanvasClass = env->FindClass(envptr, "android/graphics/Canvas"); 124 | jclass BitmapClass = env->FindClass(envptr, "android/graphics/Bitmap"); 125 | jmethodID createBitmap = env->GetStaticMethodID(envptr, BitmapClass, "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;"); 126 | jclass bmpCfgCls = env->FindClass(envptr, "android/graphics/Bitmap$Config"); 127 | jstring bitmap_mode = env->NewStringUTF(envptr, "ARGB_8888"); 128 | jmethodID bmpClsValueOfMid = env->GetStaticMethodID(envptr, bmpCfgCls, "valueOf", "(Ljava/lang/String;)Landroid/graphics/Bitmap$Config;"); 129 | jobject jBmpCfg = env->CallStaticObjectMethod(envptr, bmpCfgCls, bmpClsValueOfMid, bitmap_mode); 130 | jobject bitmap = env->CallStaticObjectMethod( envptr, BitmapClass, createBitmap, pw, ph, jBmpCfg ); 131 | jmethodID canvasConstructor = env->GetMethodID(envptr, CanvasClass, "", "(Landroid/graphics/Bitmap;)V"); 132 | jobject canvas = env->NewObject(envptr, CanvasClass, canvasConstructor, bitmap ); 133 | 134 | env->DeleteLocalRef( envptr, CanvasClass ); 135 | env->DeleteLocalRef( envptr, BitmapClass ); 136 | env->DeleteLocalRef( envptr, bmpCfgCls ); 137 | env->DeleteLocalRef( envptr, bitmap_mode ); 138 | 139 | w->BackingBitmap = env->NewGlobalRef(envptr, bitmap ); 140 | w->BackingCanvas = env->NewGlobalRef(envptr, canvas ); 141 | w->WebViewObject = env->NewGlobalRef(envptr, wvObj); 142 | w->MessageChannels = env->NewGlobalRef(envptr, messageChannels); 143 | w->w = pw; 144 | w->h = ph; 145 | 146 | env->DeleteLocalRef( envptr, WebViewClass ); 147 | env->DeleteLocalRef( envptr, activityClass ); 148 | env->DeleteLocalRef( envptr, WebSettingsClass ); 149 | env->DeleteLocalRef( envptr, ViewClass ); 150 | } 151 | 152 | int WebViewGetProgress( WebViewNativeActivityObject * obj ) 153 | { 154 | const struct JNINativeInterface * env = 0; 155 | const struct JNINativeInterface ** envptr = &env; 156 | const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; 157 | const struct JNIInvokeInterface * jnii = *jniiptr; 158 | 159 | jnii->AttachCurrentThread( jniiptr, &envptr, NULL); 160 | env = (*envptr); 161 | 162 | jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView"); 163 | jmethodID WebViewProgress = env->GetMethodID(envptr, WebViewClass, "getProgress", "()I"); 164 | int ret = env->CallIntMethod( envptr, obj->WebViewObject, WebViewProgress ); 165 | env->DeleteLocalRef( envptr, WebViewClass ); 166 | return ret; 167 | } 168 | 169 | void WebViewPostMessage( WebViewNativeActivityObject * w, const char * mesg, int initial ) 170 | { 171 | const struct JNINativeInterface * env = 0; 172 | const struct JNINativeInterface ** envptr = &env; 173 | const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; 174 | const struct JNIInvokeInterface * jnii = *jniiptr; 175 | 176 | jnii->AttachCurrentThread( jniiptr, &envptr, NULL); 177 | env = (*envptr); 178 | 179 | jclass WebMessagePortClass = env->FindClass(envptr, "android/webkit/WebMessagePort" ); 180 | jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView"); 181 | jclass WebMessageClass = env->FindClass(envptr, "android/webkit/WebMessage" ); 182 | 183 | jstring strjs = env->NewStringUTF( envptr, mesg ); 184 | 185 | if( initial ) 186 | { 187 | jobject mc1 = env->GetObjectArrayElement(envptr, w->MessageChannels, 1); 188 | jmethodID WebMessageConstructor = env->GetMethodID(envptr, WebMessageClass, "", "(Ljava/lang/String;[Landroid/webkit/WebMessagePort;)V"); 189 | 190 | //https://stackoverflow.com/questions/41753104/how-do-you-use-webmessageport-as-an-alternative-to-addjavascriptinterface 191 | // Only on initial hop do we want to post the root webmessage, which hooks up out webmessage port. 192 | jmethodID postMessageMethod = env->GetMethodID(envptr, WebViewClass, "postWebMessage", "(Landroid/webkit/WebMessage;Landroid/net/Uri;)V"); 193 | 194 | // Need to generate a new message channel array. 195 | jobjectArray jsUseWebPorts = env->NewObjectArray( envptr, 1, WebMessagePortClass, mc1); 196 | 197 | // Need Uri.EMPTY 198 | jclass UriClass = env->FindClass(envptr, "android/net/Uri" ); 199 | jfieldID EmptyField = env->GetStaticFieldID( envptr, UriClass, "EMPTY", "Landroid/net/Uri;" ); 200 | jobject EmptyURI = env->GetStaticObjectField( envptr, UriClass, EmptyField ); 201 | 202 | 203 | jobject newwm = env->NewObject(envptr, WebMessageClass, WebMessageConstructor, strjs, jsUseWebPorts ); 204 | env->CallVoidMethod( envptr, w->WebViewObject, postMessageMethod, newwm, EmptyURI ); 205 | 206 | env->DeleteLocalRef( envptr, jsUseWebPorts ); 207 | env->DeleteLocalRef( envptr, newwm ); 208 | env->DeleteLocalRef( envptr, EmptyURI ); 209 | env->DeleteLocalRef( envptr, UriClass ); 210 | } 211 | else 212 | { 213 | jobject mc0 = env->GetObjectArrayElement(envptr, w->MessageChannels, 0); 214 | jmethodID postMessageMethod = env->GetMethodID(envptr, WebMessagePortClass, "postMessage", "(Landroid/webkit/WebMessage;)V"); 215 | jmethodID WebMessageConstructor = env->GetMethodID(envptr, WebMessageClass, "", "(Ljava/lang/String;)V"); 216 | 217 | jobject newwm = env->NewObject(envptr, WebMessageClass, WebMessageConstructor, strjs ); 218 | env->CallVoidMethod( envptr, mc0, postMessageMethod, newwm ); 219 | 220 | env->DeleteLocalRef( envptr, newwm ); 221 | env->DeleteLocalRef( envptr, mc0 ); 222 | } 223 | 224 | env->DeleteLocalRef( envptr, strjs ); 225 | env->DeleteLocalRef( envptr, WebViewClass ); 226 | env->DeleteLocalRef( envptr, WebMessageClass ); 227 | env->DeleteLocalRef( envptr, WebMessagePortClass ); 228 | } 229 | 230 | void WebViewRequestRenderToCanvas( WebViewNativeActivityObject * obj ) 231 | { 232 | const struct JNINativeInterface * env = 0; 233 | const struct JNINativeInterface ** envptr = &env; 234 | const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; 235 | const struct JNIInvokeInterface * jnii = *jniiptr; 236 | 237 | jnii->AttachCurrentThread( jniiptr, &envptr, NULL); 238 | env = (*envptr); 239 | 240 | jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView"); 241 | jmethodID drawMethod = env->GetMethodID(envptr, WebViewClass, "draw", "(Landroid/graphics/Canvas;)V"); 242 | env->CallVoidMethod( envptr, obj->WebViewObject, drawMethod, obj->BackingCanvas ); 243 | env->DeleteLocalRef( envptr, WebViewClass ); 244 | } 245 | 246 | void WebViewNativeGetPixels( WebViewNativeActivityObject * obj, uint32_t * pixel_data, int w, int h ) 247 | { 248 | const struct JNINativeInterface * env = 0; 249 | const struct JNINativeInterface ** envptr = &env; 250 | const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; 251 | const struct JNIInvokeInterface * jnii = *jniiptr; 252 | 253 | jnii->AttachCurrentThread( jniiptr, &envptr, NULL); 254 | env = (*envptr); 255 | 256 | jclass BitmapClass = env->FindClass(envptr, "android/graphics/Bitmap"); 257 | jobject buffer = env->NewDirectByteBuffer(envptr, pixel_data, obj->w*obj->h*4 ); 258 | jmethodID copyPixelsBufferID = env->GetMethodID( envptr, BitmapClass, "copyPixelsToBuffer", "(Ljava/nio/Buffer;)V" ); 259 | env->CallVoidMethod( envptr, obj->BackingBitmap, copyPixelsBufferID, buffer ); 260 | 261 | int i; 262 | int num = obj->w * obj->h; 263 | for( i = 0; i < num; i++ ) pixel_data[i] = bswap_32( pixel_data[i] ); 264 | 265 | env->DeleteLocalRef( envptr, BitmapClass ); 266 | env->DeleteLocalRef( envptr, buffer ); 267 | 268 | jnii->DetachCurrentThread( jniiptr ); 269 | } 270 | 271 | void WebViewExecuteJavascript( WebViewNativeActivityObject * obj, const char * js ) 272 | { 273 | const struct JNINativeInterface * env = 0; 274 | const struct JNINativeInterface ** envptr = &env; 275 | const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; 276 | const struct JNIInvokeInterface * jnii = *jniiptr; 277 | jnii->AttachCurrentThread( jniiptr, &envptr, NULL); 278 | env = (*envptr); 279 | 280 | jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView"); 281 | jmethodID WebViewEvalJSMethod = env->GetMethodID(envptr, WebViewClass, "evaluateJavascript", "(Ljava/lang/String;Landroid/webkit/ValueCallback;)V"); 282 | 283 | //WebView.evaluateJavascript(String script, ValueCallback resultCallback) 284 | jstring strjs = env->NewStringUTF( envptr, js ); 285 | env->CallVoidMethod( envptr, obj->WebViewObject, WebViewEvalJSMethod, strjs, 0 ); // Tricky: resultCallback = 0, if you try running looper.loop() it will crash - only manually process messages. 286 | env->DeleteLocalRef( envptr, WebViewClass ); 287 | env->DeleteLocalRef( envptr, strjs ); 288 | } 289 | 290 | char * WebViewGetLastWindowTitle( WebViewNativeActivityObject * obj ) 291 | { 292 | const struct JNINativeInterface * env = 0; 293 | const struct JNINativeInterface ** envptr = &env; 294 | const struct JNIInvokeInterface ** jniiptr = gapp->activity->vm; 295 | const struct JNIInvokeInterface * jnii = *jniiptr; 296 | jnii->AttachCurrentThread( jniiptr, &envptr, NULL); 297 | env = (*envptr); 298 | 299 | jclass WebViewClass = env->FindClass(envptr, "android/webkit/WebView"); 300 | jmethodID getTitle = env->GetMethodID(envptr, WebViewClass, "getTitle", "()Ljava/lang/String;"); 301 | jobject titleObject = env->CallObjectMethod( envptr, obj->WebViewObject, getTitle ); 302 | char *nativeString = strdup( env->GetStringUTFChars(envptr, titleObject, 0) ); 303 | env->DeleteLocalRef( envptr, titleObject ); 304 | env->DeleteLocalRef( envptr, WebViewClass ); 305 | 306 | return nativeString; 307 | } 308 | 309 | 310 | #endif 311 | #endif 312 | 313 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Table of Contents 2 | 3 | - [Clay](#clay) 4 | - [rawdrawandroid](#rawdrawandroid) 5 | - [why?](#why) 6 | - [Development Environment](#development-environment) 7 | - [Linux install Android Studio with NDK.](#linux-install-android-studio-with-ndk) 8 | - [Steps for GUI-less install (Windows, WSL)](#steps-for-gui-less-install-windows-wsl) 9 | - [Extra note for actually deploying to device in Windows](#extra-note-for-actually-deploying-to-device-in-windows) 10 | - [Rest of steps](#rest-of-steps) 11 | - [If you are going to use this](#if-you-are-going-to-use-this) 12 | - [Helper functions](#helper-functions) 13 | - [Departures from regular rawdraw.](#departures-from-regular-rawdraw) 14 | - [Google Play](#google-play) 15 | - [Part 0: Changes to your app.](#part-0-changes-to-your-app) 16 | - [Keys: You will want a key for yourself that's a real key. Not the fake one.](#keys--you-will-want-a-key-for-yourself-thats-a-real-key--not-the-fake-one) 17 | - Let Google create and manage my app signing key (recommended) 18 | - [Export and upload a key and certificate from a Java keystore](#export-and-upload-a-key-and-certificate-from-a-java-keystore) 19 | - [Prepping your app for upload.](#prepping-your-app-for-upload) 20 | - [Pre-SDK-32-Tools](#pre-sdk-32-tools) 21 | - [TODO](#todo) 22 | 23 | # Clay 24 | 25 | Initial implementation of [Clay](https://github.com/nicbarker/clay) for Android using rawdrawandroid to allow you to implement a full UI with only C. 26 | 27 | 28 | 29 | This is just a proof of concept, but the implemented parts fully work. 30 | 31 | # rawdrawandroid 32 | 33 | Ever wanted to write C code and run it on Android? Sick of multi-megabyte 34 | packages just to do the most basic of things. Well, this is a demo of how 35 | to make your own APKs and build, install and automatically run them in about 36 | 2 seconds, and with an apk size of about 25kB (with API 26). API 30 (Android R+) 37 | is unfortunately at 45kB to support ARM64 + ARM32. 38 | 39 | With this framework you get a demo which has: 40 | * To make a window with OpenGL ES support 41 | * Accelerometer/gyro input, multi-touch 42 | * An android keyboard for key input 43 | * Ability to store asset files in your APK and read them with `AAssetManager` 44 | * Permissions support for using things like sound. Example in https://github.com/cnlohr/cnfa / https://github.com/cntools/cnfa/blob/d271e0196d81412032eeffa634a94a1aaf0060a7/CNFA_android.c#L305 45 | * Directly access USB devices. Example in https://github.com/cnlohr/androidusbtest 46 | 47 | [![Youtube Video](http://img.youtube.com/vi/Cz_LvaN36Ag/0.jpg)](http://www.youtube.com/watch?v=Cz_LvaN36Ag "") 48 | 49 | DISCLAIMER: I take no warranty or responsibility for this code. Use at your own risk. I've never released an app on the app store, so there may be some fundamental issue with using this toolset to make commercial apps! 50 | 51 | For support, we have a Discord, but it is not public at the moment, feel free to reach out to @cnlohr on Discord, with a short message as to why you'd like to join, and he can send you an invite! 52 | 53 | # Why? 54 | 55 | Because sometimes you want to do things that don't fit into the normal way of doing it and all the discussions online revolve around doing it with all the normal processes. And those processes change, making it difficult to keep up and do specific things. By using `Makefile`s it's easy to see what exact commands are executed and add custom rules and tweak your build. C is a universal language. Rawdraw operates on everything from an ESP8266, to RaspberryPi, Windows Linux and now, even Android. Write code once, use it everywhere. 56 | 57 | When you don't fill your build process with hills of beans, you end up being left with the parts that are important, and not the frivilous parts. This makes it easier to develop, deploy, etc, because everything takes much less time. 58 | 59 | A little bit of this also has to do to stick it to all those Luddites on the internet who post "that's impossible" or "you're doing it wrong" to Stack Overflow questions... Requesting permissions in the JNI "oh you **have** to do that in Java" or other dumb stuff like that. I am completely uninterested in your opinions of what is or is not possible. This is computer science. There aren't restrictions. I can do anything I want. It's just bits. You don't own me. 60 | 61 | P.S. If you want a bunch of examples of how to do a ton of things in C on Android that you "need" java for, scroll to the bottom of this file: https://github.com/cntools/rawdraw/blob/master/CNFGEGLDriver.c - it shows how to use the JNI to marshall a ton of stuff to/from the Android API without needing to jump back into Java/Kotlin land. 62 | 63 | # Development Environment 64 | 65 | Most of the testing was done on Linux, however @AEFeinstein has done at least cursory testing in Windows. You still need some components of Android studio set up to use this, so it's generally easier to just install Android studio completely, but there are instructions on sort of how to do it piecemeal for Windows. 66 | 67 | ## Linux install Android Studio with NDK. 68 | 69 | This set of steps describes how to install Android Studio with NDK support in Linux. It uses the graphical installer and installs a lot more stuff than the instructions below. You may be able to mix-and-match these two sets of instructions. For instance if you are on Linux but don't want to sacrifice 6 GB of disk to the Googs. 70 | 71 | **NOTE** You probably should use the WSL instructions instead of these instructions as it will produc a more lean installation. 72 | 73 | 1) Install prerequisites: 74 | ``` 75 | sudo apt install openjdk-11-jdk-headless adb 76 | ``` 77 | 2) Download Android Studio: https://developer.android.com/studio 78 | 3) Start 'studio.sh' in android-studio/bin 79 | 4) Let it install the SDK. 80 | 5) Go to sdkmanager ("Configure" button in bottom right) 81 | 6) Probably want to use Android 24, so select that from the list. 82 | 7) Select "SDK Tools" -> "NDK (Side-by-side)" 83 | 8) Download this repo 84 | ``` 85 | git clone https://github.com/cnlohr/rawdrawandroid --recurse-submodules 86 | cd rawdrawandroid 87 | ``` 88 | 9) Turn on developer mode on your phone (will vary depending on android version) 89 | 10) Make your own key 90 | ``` 91 | make keystore 92 | ``` 93 | 11) Go into developer options on your phone and enable "USB debugging" make sure to select always allow. 94 | 12) Plug your phone into the computer. 95 | 13) Run your program. 96 | ``` 97 | make push run 98 | ``` 99 | 100 | ## Steps for GUI-less install (Windows, WSL) 101 | 102 | If you're developing in Windows Subsystem for Linux (WSL), follow the "Steps for GUI-less install" to install the Android components from the command line, without any GUI components. 103 | 104 | ### Extra note for actually deploying to device in Windows 105 | 106 | In order to push the APK to your phone, you need `adb` installed in Windows as well. You can do that by getting the full Android Studio from https://developer.android.com/studio#downloads or directly https://dl.google.com/android/repository/platform-tools_r24.0.4-windows.zip. Installing the full Android Studio is easier, but you can also get the "Command line tools only" and install `adb` from there. The steps below outline how to do this with the direct link. 107 | 108 | ### Rest of steps 109 | 110 | 1. Install Windows Subsystem for Linux (WSL). You can find instructions here: https://docs.microsoft.com/en-us/windows/wsl/install-win10 - we use "Ubuntu" for this. 111 | 112 | 2. Install prerequisites: 113 | ``` 114 | sudo apt install openjdk-17-jdk-headless adb unzip zip 115 | ``` 116 | 2. Download "Command line tools only": https://developer.android.com/studio#downloads - you can get a URL and use `wget` in WSL to download the tools by clicking on the **"Linux"** toolset, then right-clicking on the accept link and saying copy link to location. Then you can say `wget ` in WSL. 117 | 3. Create a folder for the Android SDK and export it. You may want to add that export to your `~/.bashrc`: 118 | ``` 119 | mkdir ~/android-sdk 120 | export ANDROID_HOME=~/android-sdk 121 | printf "\nexport ANDROID_HOME=~/android-sdk\n" >> ~/.bashrc 122 | ``` 123 | 4. Unzip the "Command line tools only" file so that `tools` is in your brand new `android-sdk` folder. 124 | 5. Install the SDK and NDK components: 125 | 126 | For earler versions of tools see note for pre-SDK-32-Tools. 127 | 128 | If your platform command-line tools are **30**, the command-line tools will be placed in the cmdline-tools folder. So, you will need to execute the following: 129 | ``` 130 | yes | $ANDROID_HOME/cmdline-tools/bin/sdkmanager --sdk_root=${ANDROID_HOME} --licenses 131 | $ANDROID_HOME/cmdline-tools/bin/sdkmanager --sdk_root=${ANDROID_HOME} "build-tools;30.0.2" "cmake;3.10.2.4988404" "ndk;21.3.6528147" "platform-tools" "platforms;android-30" "tools" 132 | ``` 133 | 134 | If you want to target Android 34 (not recommended in 2024, for device compatibility, you will need to execute the following): 135 | ``` 136 | yes | $ANDROID_HOME/cmdline-tools/bin/sdkmanager --sdk_root=${ANDROID_HOME} --licenses 137 | $ANDROID_HOME/cmdline-tools/bin/sdkmanager --sdk_root=${ANDROID_HOME} "build-tools;34.0.0" "ndk;26.2.11394342" "platform-tools" "platforms;android-34" "tools" "cmake;3.10.2.4988404" 138 | ``` 139 | 140 | **NOTE** If you are upgrading NDK versions, you may need to remove old versions, this Makefile does not necessarily do the best job at auto-selecting NDK versions. 141 | 142 | You can see all avialable versions of software with this command: 143 | ``` 144 | $ANDROID_HOME/cmdline-tools/bin/sdkmanager --list --sdk_root=${ANDROID_HOME} 145 | ``` 146 | 147 | 6. Install the Windows ADB toolset. 148 | ``` 149 | mkdir -p $ANDROID_HOME/windows 150 | cd $ANDROID_HOME/windows 151 | wget https://dl.google.com/android/repository/platform-tools_r24.0.4-windows.zip 152 | unzip platform-tools_r24.0.4-windows.zip 153 | export ADB=$ANDROID_HOME/windows/platform-tools/adb.exe 154 | printf "\nexport ADB=$ANDROID_HOME/windows/platform-tools/adb.exe\n" >> ~/.bashrc 155 | ``` 156 | 157 | Alternatively, you may want to use https://dl.google.com/android/repository/platform-tools_r30.0.5-windows.zip for r30. 158 | 159 | 6. NOTE: because of updates to environment variables, you may want to close and re-open your WSL terminal. 160 | 7. Download this repo 161 | ``` 162 | git clone https://github.com/cnlohr/rawdrawandroid --recurse-submodules 163 | cd rawdrawandroid 164 | ``` 165 | 8. Turn on developer mode on your phone (will vary depending on android version) 166 | 9. Go into developer options on your phone and enable "USB debugging" make sure to select always allow. 167 | 10. Plug your phone into the computer. 168 | 11. Make your keystore. 169 | ``` 170 | make keystore 171 | ``` 172 | 12. Compile and run your program. 173 | ``` 174 | make run 175 | ``` 176 | 177 | # If you are going to use this 178 | * Check out the example here: https://github.com/cnlohr/rawdrawandroidexample 179 | * You may want to copy-and-paste this project, but, you could probably use it as a submodule. You may also want to copy-and-paste the submodule. 180 | * You *MUST* override the app name. 181 | - See in Makefile `APPNAME` and `PACKAGENAME` you should be able to include this project's makefile and override that. 182 | - You must also update `AndroidManifest.xml` with whatever name and org you plan to use. 183 | - You will need to update: `package` in `` to be your `PACKAGENAME` variable in Makefile. 184 | - Both `android:label` labels need to reflect your new app name. They are in your `` and `` sections. 185 | - Update the `android:value` field in `android.app.lib_name` 186 | 187 | * If you are using permission you have to prompt for, you must both add it to your `AndroidManifest.xml` as well as check if you have it, and if not, prompt the user. See helper functions below. You can see an example of this with `sound_android.c` from ColorChord. https://github.com/cnlohr/colorchord/blob/master/colorchord2/sound_android.c 188 | * Be sure to uninstall any previously installed apps which would look like this app, if you have a different build by the same name signed with another key, bad things will happen. 189 | * You can see your log with: 190 | ``` 191 | adb logcat 192 | ``` 193 | * If your app opens and closes instantly, try seeing if there are any missing symbols: 194 | ``` 195 | adb logcat | grep UnsatisfiedLinkError 196 | ``` 197 | 198 | 199 | # Helper functions 200 | 201 | Because we are doing this entirelly in the NDK, with the JNI, we won't have the luxury of writing any Java/Kotlin code and calling it. That means all of the examples online have to be heavily marshalled. In rawdraw's EGL driver, we have many examples of how to do that. That said, you can use the following functions which get you most of the way there. 202 | 203 | `struct android_app * gapp;` 204 | 205 | `int AndroidHasPermissions(const char* perm_name);` 206 | 207 | `void AndroidRequestAppPermissions(const char * perm);` 208 | 209 | `void AndroidDisplayKeyboard(int pShow);` 210 | 211 | `int AndroidGetUnicodeChar( int keyCode, int metaState );` 212 | 213 | `int android_width, android_height;` 214 | 215 | `extern int android_sdk_version; //Derived at start from property ro.build.version.sdk` 216 | 217 | 218 | # Departures from regular rawdraw. 219 | 220 | Also, above and beyond rawdraw, you *must* implement the following two functions to handle when your apps is suspended, resumed or has its window terminated. 221 | 222 | `void HandleResume();` 223 | `void HandleSuspend();` 224 | `void HandleWindowTermination();` 225 | 226 | In addition to that, the syntax of `HandleMotion(...)` is different, in that instead of the `mask` variable being a mask, it is simply updating that specific pointer. 227 | 228 | # Google Play 229 | 230 | As it turns out, Google somehow lets apps built with this onto the store. Like ColorChord https://github.com/cnlohr/colorchord. 231 | 232 | ## Part 0: Changes to your app. 233 | 234 | 1. Make sure you are using the newest SDK. 235 | 2. You will need to add a versionCode to your `AndroidManifest.xml`. In your `AndroidManifest.xml`, add `android:versionCode="integer"` to the tag where "integer" is a version number. 236 | 3. In your `AndroidManifest.xml`, change `android:debuggable` to false. 237 | 4. You may want to support multiple platforms natively. Add the following to your `Makefile`: `TARGETS:=makecapk/lib/arm64-v8a/lib$(APPNAME).so makecapk/lib/armeabi-v7a/lib$(APPNAME).so makecapk/lib/x86/lib$(APPNAME).so makecapk/lib/x86_64/lib$(APPNAME).so` 238 | 5. You will need to specify target and Min SDK in your `AndroidManifest.xml` See: `` 239 | 6. Those target / min versions must match your Makefile. Note that without a `minSdkVerson` google will wrongfully assume 1. This is dangerous. Be sure to test your app on a device with whichever minSdkVersion you've specified. 240 | 7. You will need to disable the debuggable flag in your app. See `` 241 | 242 | 243 | Get a google play account. Details surrounding app creation are outside the scope of this readme. When getting ready to upload your APK. 244 | 245 | ## Keys: You will want a key for yourself that's a real key. Not the fake one. 246 | 247 | First you will need to make a real key. This can be accomplished by deleting our fake key `my-release-key.keystore` and executing the following command (being careful to fill `####` in with real info): 248 | 249 | ```make keystore STOREPASS=#### DNAME="\"CN=####, OU=ID, O=####, L=####, S=####, C=####\"" ALIASNAME=####``` 250 | 251 | The alias name will be `standkey`. You will want to verify you can build your app with this key. Be sure to fill in STOREPASS the same. 252 | 253 | ```make clean run STOREPASS=####``` 254 | 255 | 256 | ## Let Google create and manage my app signing key (recommended) 257 | 258 | 259 | ## Export and upload a key and certificate from a Java keystore 260 | 261 | If you want to use the play store key with "Export and upload a key and certificate from a Java keystore" Instead of `Let Google create and manage my app signing key (recommended)` and follow PEKP instructions. 262 | 263 | ## Prepping your app for upload. 264 | 265 | You MUST have aligned ZIPs for the Play store. You must run the following command: 266 | 267 | ```zipalign -c -v 8 makecapk.apk``` 268 | 269 | Upload your APK `makecapk.apk` made with your key. 270 | 271 | ## Pre-SDK-32-Tools 272 | 273 | If you are using **Android 29 or older**, do this. 274 | ``` 275 | yes | $ANDROID_HOME/tools/bin/sdkmanager --sdk_root=${ANDROID_HOME} --licenses 276 | $ANDROID_HOME/tools/bin/sdkmanager --sdk_root=${ANDROID_HOME} "build-tools;29.0.3" "cmake;3.10.2.4988404" "ndk;21.1.6352462" "patcher;v4" "platform-tools" "platforms;android-30" "tools" 277 | ``` 278 | 279 | If your platform command-line tools are **30**, the command-line tools will be placed in the cmdline-tools folder. So, you will need to execute the following: 280 | ``` 281 | yes | $ANDROID_HOME/cmdline-tools/bin/sdkmanager --sdk_root=${ANDROID_HOME} --licenses 282 | $ANDROID_HOME/cmdline-tools/bin/sdkmanager --sdk_root=${ANDROID_HOME} "build-tools;30.0.2" "cmake;3.10.2.4988404" "ndk;21.3.6528147" "patcher;v4" "platform-tools" "platforms;android-30" "tools" 283 | ``` 284 | 285 | If your platform command-line tools are **32**, the command-line tools will be placed in the cmdline-tools folder. So, you will need to execute the following (This appears to be backwards compatbile to some degree with Android 30): 286 | ``` 287 | yes | $ANDROID_HOME/cmdline-tools/bin/sdkmanager --sdk_root=${ANDROID_HOME} --licenses 288 | $ANDROID_HOME/cmdline-tools/bin/sdkmanager --sdk_root=${ANDROID_HOME} "build-tools;32.0.0" "cmake;3.22.1" "ndk;25.1.8937393" "platforms;android-32" "patcher;v4" "platform-tools" "tools" 289 | ``` 290 | 291 | 292 | # TODO 293 | 294 | Try a bunch of these cool priveleges, see what they all do. 295 | * permission.ACCESS 296 | * permission.INTERNET 297 | * permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS 298 | * permission.ACCESS_NETWORK_STATE 299 | * permission.WRITE_EXTERNAL_STORAGE 300 | * permission.READ_PHONE_STATE 301 | * permission.GET_TASKS 302 | * permission.REORDER_TASKS 303 | * permission.WRITE_APN_SETTINGS 304 | * permission.READ_SECURE_SETTINGS 305 | * permission.READ_SETTINGS 306 | * permission.REAL_GET_TASKS 307 | * permission.INTERACT_ACROSS_USERS 308 | * permission.MANAGE_USERS 309 | * permission.INSTALL_PACKAGES 310 | * permission.DELETE_PACKAGES 311 | * permission.INTERACT_ACROSS_USERS_FULL 312 | * permission.READ_MEDIA_STORAGE 313 | * permission.WRITE_MEDIA_STORAGE 314 | * android.permission.VR 315 | * android.permission.INSTALL_PACKAGES 316 | 317 | 318 | 319 | -------------------------------------------------------------------------------- /android_native_app_glue.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2010 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "android_native_app_glue.h" 27 | #include 28 | #include 29 | 30 | #include "webview_native_activity.h" 31 | struct android_app * gapp; 32 | 33 | #define LOGI(...) ((void)printf(__VA_ARGS__)) 34 | #define LOGE(...) ((void)printf(__VA_ARGS__)) 35 | 36 | /* For debug builds, always enable the debug traces in this library */ 37 | 38 | #ifndef NDEBUG 39 | # define LOGV(...) ((void)printf(__VA_ARGS__)) 40 | #else 41 | # define LOGV(...) ((void)0) 42 | #endif 43 | 44 | 45 | typedef struct __attribute__((packed)) 46 | { 47 | void (*callback)( void * ); 48 | void * opaque; 49 | } MainThreadCallbackProps; 50 | 51 | static int pfd[2]; 52 | pthread_t debug_capture_thread; 53 | static void * debug_capture_thread_fn( void * v ) 54 | { 55 | //struct android_app * app = (struct android_app*)v; 56 | ssize_t readSize; 57 | char buf[2048]; 58 | 59 | while((readSize = read(pfd[0], buf, sizeof buf - 1)) > 0) { 60 | if(buf[readSize - 1] == '\n') { 61 | --readSize; 62 | } 63 | buf[readSize] = 0; // add null-terminator 64 | __android_log_write(ANDROID_LOG_DEBUG, APPNAME, buf); // Set any log level you want 65 | #ifdef RDALOGFNCB 66 | extern void RDALOGFNCB( int size, char * buf ); 67 | RDALOGFNCB( readSize, buf ); 68 | #endif 69 | //if( debug_capture_hook_function ) debug_capture_hook_function( readSize, buf ); 70 | } 71 | return 0; 72 | } 73 | 74 | static void free_saved_state(struct android_app* android_app) { 75 | pthread_mutex_lock(&android_app->mutex); 76 | if (android_app->savedState != NULL) { 77 | free(android_app->savedState); 78 | android_app->savedState = NULL; 79 | android_app->savedStateSize = 0; 80 | } 81 | pthread_mutex_unlock(&android_app->mutex); 82 | } 83 | 84 | int8_t android_app_read_cmd(struct android_app* android_app) { 85 | int8_t cmd; 86 | if (read(android_app->msgread, &cmd, sizeof(cmd)) == sizeof(cmd)) { 87 | switch (cmd) { 88 | case APP_CMD_SAVE_STATE: 89 | free_saved_state(android_app); 90 | break; 91 | } 92 | return cmd; 93 | } else { 94 | LOGE("No data on command pipe!"); 95 | } 96 | return -1; 97 | } 98 | 99 | static void print_cur_config(struct android_app* android_app) { 100 | //For additional debugging this can be enabled, but for now - no need for the extra space. 101 | /* 102 | char lang[2], country[2]; 103 | AConfiguration_getLanguage(android_app->config, lang); 104 | AConfiguration_getCountry(android_app->config, country); 105 | 106 | LOGV("Config: mcc=%d mnc=%d lang=%c%c cnt=%c%c orien=%d touch=%d dens=%d " 107 | "keys=%d nav=%d keysHid=%d navHid=%d sdk=%d size=%d long=%d " 108 | "modetype=%d modenight=%d", 109 | AConfiguration_getMcc(android_app->config), 110 | AConfiguration_getMnc(android_app->config), 111 | lang[0], lang[1], country[0], country[1], 112 | AConfiguration_getOrientation(android_app->config), 113 | AConfiguration_getTouchscreen(android_app->config), 114 | AConfiguration_getDensity(android_app->config), 115 | AConfiguration_getKeyboard(android_app->config), 116 | AConfiguration_getNavigation(android_app->config), 117 | AConfiguration_getKeysHidden(android_app->config), 118 | AConfiguration_getNavHidden(android_app->config), 119 | AConfiguration_getSdkVersion(android_app->config), 120 | AConfiguration_getScreenSize(android_app->config), 121 | AConfiguration_getScreenLong(android_app->config), 122 | AConfiguration_getUiModeType(android_app->config), 123 | AConfiguration_getUiModeNight(android_app->config)); 124 | */ 125 | } 126 | 127 | void android_app_pre_exec_cmd(struct android_app* android_app, int8_t cmd) { 128 | switch (cmd) { 129 | case APP_CMD_INPUT_CHANGED: 130 | LOGV("APP_CMD_INPUT_CHANGED\n"); 131 | pthread_mutex_lock(&android_app->mutex); 132 | if (android_app->inputQueue != NULL) { 133 | AInputQueue_detachLooper(android_app->inputQueue); 134 | } 135 | android_app->inputQueue = android_app->pendingInputQueue; 136 | if (android_app->inputQueue != NULL) { 137 | LOGV("Attaching input queue to looper"); 138 | AInputQueue_attachLooper(android_app->inputQueue, 139 | android_app->looper, LOOPER_ID_INPUT, NULL, 140 | &android_app->inputPollSource); 141 | } 142 | pthread_cond_broadcast(&android_app->cond); 143 | pthread_mutex_unlock(&android_app->mutex); 144 | break; 145 | 146 | case APP_CMD_INIT_WINDOW: 147 | LOGV("APP_CMD_INIT_WINDOW\n"); 148 | pthread_mutex_lock(&android_app->mutex); 149 | android_app->window = android_app->pendingWindow; 150 | pthread_cond_broadcast(&android_app->cond); 151 | pthread_mutex_unlock(&android_app->mutex); 152 | break; 153 | 154 | case APP_CMD_TERM_WINDOW: 155 | LOGV("APP_CMD_TERM_WINDOW\n"); 156 | pthread_cond_broadcast(&android_app->cond); 157 | break; 158 | 159 | case APP_CMD_RESUME: 160 | case APP_CMD_START: 161 | case APP_CMD_PAUSE: 162 | case APP_CMD_STOP: 163 | LOGV("activityState=%d\n", cmd); 164 | pthread_mutex_lock(&android_app->mutex); 165 | android_app->activityState = cmd; 166 | pthread_cond_broadcast(&android_app->cond); 167 | pthread_mutex_unlock(&android_app->mutex); 168 | break; 169 | 170 | case APP_CMD_CONFIG_CHANGED: 171 | LOGV("APP_CMD_CONFIG_CHANGED\n"); 172 | AConfiguration_fromAssetManager(android_app->config, 173 | android_app->activity->assetManager); 174 | print_cur_config(android_app); 175 | break; 176 | 177 | case APP_CMD_DESTROY: 178 | LOGV("APP_CMD_DESTROY\n"); 179 | android_app->destroyRequested = 1; 180 | break; 181 | } 182 | } 183 | 184 | void android_app_post_exec_cmd(struct android_app* android_app, int8_t cmd) { 185 | switch (cmd) { 186 | case APP_CMD_TERM_WINDOW: 187 | LOGV("APP_CMD_TERM_WINDOW\n"); 188 | pthread_mutex_lock(&android_app->mutex); 189 | android_app->window = NULL; 190 | pthread_cond_broadcast(&android_app->cond); 191 | pthread_mutex_unlock(&android_app->mutex); 192 | break; 193 | 194 | case APP_CMD_SAVE_STATE: 195 | LOGV("APP_CMD_SAVE_STATE\n"); 196 | pthread_mutex_lock(&android_app->mutex); 197 | android_app->stateSaved = 1; 198 | pthread_cond_broadcast(&android_app->cond); 199 | pthread_mutex_unlock(&android_app->mutex); 200 | break; 201 | 202 | case APP_CMD_RESUME: 203 | free_saved_state(android_app); 204 | break; 205 | } 206 | } 207 | 208 | void app_dummy() { 209 | 210 | } 211 | 212 | static void android_app_destroy(struct android_app* android_app) { 213 | LOGV("android_app_destroy!"); 214 | free_saved_state(android_app); 215 | pthread_mutex_lock(&android_app->mutex); 216 | if (android_app->inputQueue != NULL) { 217 | AInputQueue_detachLooper(android_app->inputQueue); 218 | } 219 | AConfiguration_delete(android_app->config); 220 | android_app->destroyed = 1; 221 | pthread_cond_broadcast(&android_app->cond); 222 | pthread_mutex_unlock(&android_app->mutex); 223 | // Can't touch android_app object after this. 224 | } 225 | 226 | static void process_input(struct android_app* app, struct android_poll_source* source) { 227 | AInputEvent* event = NULL; 228 | while (AInputQueue_getEvent(app->inputQueue, &event) >= 0) { 229 | //LOGV("New input event: type=%d\n", AInputEvent_getType(event)); 230 | if (AInputQueue_preDispatchEvent(app->inputQueue, event)) { 231 | continue; 232 | } 233 | int32_t handled = 0; 234 | if (app->onInputEvent != NULL) handled = app->onInputEvent(app, event); 235 | AInputQueue_finishEvent(app->inputQueue, event, handled); 236 | } 237 | } 238 | 239 | static int process_ui( int dummy1, int dummy2, void * dummy3 ) { 240 | // Can't trust parameters in UI thread callback. 241 | MainThreadCallbackProps rep; 242 | read(gapp->uimsgread, &rep, sizeof(rep)); 243 | rep.callback( rep.opaque ); 244 | return 1; 245 | } 246 | 247 | static void process_cmd(struct android_app* app, struct android_poll_source* source) { 248 | int8_t cmd = android_app_read_cmd(app); 249 | android_app_pre_exec_cmd(app, cmd); 250 | if (app->onAppCmd != NULL) app->onAppCmd(app, cmd); 251 | android_app_post_exec_cmd(app, cmd); 252 | } 253 | 254 | static void* android_app_entry(void* param) { 255 | struct android_app* android_app = (struct android_app*)param; 256 | 257 | android_app->config = AConfiguration_new(); 258 | AConfiguration_fromAssetManager(android_app->config, android_app->activity->assetManager); 259 | 260 | print_cur_config(android_app); 261 | android_app->cmdPollSource.id = LOOPER_ID_MAIN; 262 | android_app->cmdPollSource.app = android_app; 263 | android_app->cmdPollSource.process = process_cmd; 264 | android_app->inputPollSource.id = LOOPER_ID_INPUT; 265 | android_app->inputPollSource.app = android_app; 266 | android_app->inputPollSource.process = process_input; 267 | 268 | ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS); 269 | ALooper_addFd(looper, android_app->msgread, LOOPER_ID_MAIN, ALOOPER_EVENT_INPUT, NULL, &android_app->cmdPollSource); 270 | android_app->looper = looper; 271 | 272 | pthread_mutex_lock(&android_app->mutex); 273 | android_app->running = 1; 274 | pthread_cond_broadcast(&android_app->cond); 275 | pthread_mutex_unlock(&android_app->mutex); 276 | 277 | android_main(android_app); 278 | 279 | android_app_destroy(android_app); 280 | return NULL; 281 | } 282 | 283 | // -------------------------------------------------------------------- 284 | // Native activity interaction (called from main thread) 285 | // -------------------------------------------------------------------- 286 | 287 | static struct android_app* android_app_create(ANativeActivity* activity, 288 | void* savedState, size_t savedStateSize) { 289 | struct android_app* android_app = (struct android_app*)malloc(sizeof(struct android_app)); 290 | memset(android_app, 0, sizeof(struct android_app)); 291 | android_app->activity = activity; 292 | 293 | pthread_mutex_init(&android_app->mutex, NULL); 294 | pthread_cond_init(&android_app->cond, NULL); 295 | 296 | 297 | pthread_attr_t attr; 298 | pthread_attr_init(&attr); 299 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 300 | 301 | //Capture input 302 | setvbuf(stdout, 0, _IOLBF, 0); // make stdout line-buffered 303 | setvbuf(stderr, 0, _IONBF, 0); // make stderr unbuffered 304 | pipe(pfd); 305 | dup2(pfd[1], 1); 306 | dup2(pfd[1], 2); 307 | pthread_create(&debug_capture_thread, &attr, debug_capture_thread_fn, android_app); 308 | 309 | if (savedState != NULL) { 310 | android_app->savedState = malloc(savedStateSize); 311 | android_app->savedStateSize = savedStateSize; 312 | memcpy(android_app->savedState, savedState, savedStateSize); 313 | } 314 | 315 | int msgpipe[2]; 316 | if (pipe(msgpipe)) { 317 | LOGE("could not create pipe: %s", strerror(errno)); 318 | return NULL; 319 | } 320 | android_app->msgread = msgpipe[0]; 321 | android_app->msgwrite = msgpipe[1]; 322 | 323 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 324 | // Handle calling events on the UI thread. You can get callbacks with RunCallbackOnUIThread. 325 | int msgpipemain[2]; 326 | if (pipe(msgpipemain)) { 327 | LOGE("could not create pipe: %s", strerror(errno)); 328 | return NULL; 329 | } 330 | android_app->uimsgread = msgpipemain[0]; 331 | android_app->uimsgwrite = msgpipemain[1]; 332 | ALooper * looper = ALooper_forThread(); 333 | ALooper_addFd(looper, android_app->uimsgread, LOOPER_ID_MAIN_THREAD, ALOOPER_EVENT_INPUT, process_ui, gapp); //NOTE: Cannot use NULL callback 334 | android_app->looperui = looper; 335 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 336 | 337 | pthread_attr_init(&attr); 338 | pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 339 | pthread_create(&android_app->thread, &attr, android_app_entry, android_app); 340 | 341 | // Wait for thread to start. 342 | pthread_mutex_lock(&android_app->mutex); 343 | while (!android_app->running) { 344 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 345 | } 346 | pthread_mutex_unlock(&android_app->mutex); 347 | 348 | return android_app; 349 | } 350 | 351 | static void android_app_write_cmd(struct android_app* android_app, int8_t cmd) { 352 | if (write(android_app->msgwrite, &cmd, sizeof(cmd)) != sizeof(cmd)) { 353 | LOGE("Failure writing android_app cmd: %s\n", strerror(errno)); 354 | } 355 | } 356 | 357 | static void android_app_set_input(struct android_app* android_app, AInputQueue* inputQueue) { 358 | pthread_mutex_lock(&android_app->mutex); 359 | android_app->pendingInputQueue = inputQueue; 360 | android_app_write_cmd(android_app, APP_CMD_INPUT_CHANGED); 361 | while (android_app->inputQueue != android_app->pendingInputQueue) { 362 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 363 | } 364 | pthread_mutex_unlock(&android_app->mutex); 365 | } 366 | 367 | static void android_app_set_window(struct android_app* android_app, ANativeWindow* window) { 368 | pthread_mutex_lock(&android_app->mutex); 369 | if (android_app->pendingWindow != NULL) { 370 | android_app_write_cmd(android_app, APP_CMD_TERM_WINDOW); 371 | } 372 | android_app->pendingWindow = window; 373 | if (window != NULL) { 374 | android_app_write_cmd(android_app, APP_CMD_INIT_WINDOW); 375 | } 376 | while (android_app->window != android_app->pendingWindow) { 377 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 378 | } 379 | pthread_mutex_unlock(&android_app->mutex); 380 | } 381 | 382 | static void android_app_set_activity_state(struct android_app* android_app, int8_t cmd) { 383 | pthread_mutex_lock(&android_app->mutex); 384 | android_app_write_cmd(android_app, cmd); 385 | while (android_app->activityState != cmd) { 386 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 387 | } 388 | pthread_mutex_unlock(&android_app->mutex); 389 | } 390 | 391 | static void android_app_free(struct android_app* android_app) { 392 | pthread_mutex_lock(&android_app->mutex); 393 | android_app_write_cmd(android_app, APP_CMD_DESTROY); 394 | while (!android_app->destroyed) { 395 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 396 | } 397 | pthread_mutex_unlock(&android_app->mutex); 398 | 399 | close(android_app->msgread); 400 | close(android_app->msgwrite); 401 | pthread_cond_destroy(&android_app->cond); 402 | pthread_mutex_destroy(&android_app->mutex); 403 | free(android_app); 404 | } 405 | 406 | static void onDestroy(ANativeActivity* activity) { 407 | LOGV("Destroy: %p\n", activity); 408 | android_app_free((struct android_app*)activity->instance); 409 | } 410 | 411 | static void onStart(ANativeActivity* activity) { 412 | LOGV("Start: %p\n", activity); 413 | android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_START); 414 | } 415 | 416 | static void onResume(ANativeActivity* activity) { 417 | LOGV("Resume: %p\n", activity); 418 | android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_RESUME); 419 | } 420 | 421 | static void* onSaveInstanceState(ANativeActivity* activity, size_t* outLen) { 422 | struct android_app* android_app = (struct android_app*)activity->instance; 423 | void* savedState = NULL; 424 | 425 | LOGV("SaveInstanceState: %p\n", activity); 426 | pthread_mutex_lock(&android_app->mutex); 427 | android_app->stateSaved = 0; 428 | android_app_write_cmd(android_app, APP_CMD_SAVE_STATE); 429 | while (!android_app->stateSaved) { 430 | pthread_cond_wait(&android_app->cond, &android_app->mutex); 431 | } 432 | 433 | if (android_app->savedState != NULL) { 434 | savedState = android_app->savedState; 435 | *outLen = android_app->savedStateSize; 436 | android_app->savedState = NULL; 437 | android_app->savedStateSize = 0; 438 | } 439 | 440 | pthread_mutex_unlock(&android_app->mutex); 441 | 442 | return savedState; 443 | } 444 | 445 | static void onPause(ANativeActivity* activity) { 446 | LOGV("Pause: %p\n", activity); 447 | android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_PAUSE); 448 | } 449 | 450 | static void onStop(ANativeActivity* activity) { 451 | LOGV("Stop: %p\n", activity); 452 | android_app_set_activity_state((struct android_app*)activity->instance, APP_CMD_STOP); 453 | } 454 | 455 | static void onConfigurationChanged(ANativeActivity* activity) { 456 | struct android_app* android_app = (struct android_app*)activity->instance; 457 | LOGV("ConfigurationChanged: %p\n", activity); 458 | android_app_write_cmd(android_app, APP_CMD_CONFIG_CHANGED); 459 | } 460 | 461 | static void onLowMemory(ANativeActivity* activity) { 462 | struct android_app* android_app = (struct android_app*)activity->instance; 463 | LOGV("LowMemory: %p\n", activity); 464 | android_app_write_cmd(android_app, APP_CMD_LOW_MEMORY); 465 | } 466 | 467 | static void onWindowFocusChanged(ANativeActivity* activity, int focused) { 468 | LOGV("WindowFocusChanged: %p -- %d\n", activity, focused); 469 | android_app_write_cmd((struct android_app*)activity->instance, 470 | focused ? APP_CMD_GAINED_FOCUS : APP_CMD_LOST_FOCUS); 471 | } 472 | 473 | static void onNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) { 474 | LOGV("NativeWindowCreated: %p -- %p\n", activity, window); 475 | android_app_set_window((struct android_app*)activity->instance, window); 476 | } 477 | 478 | static void onNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) { 479 | LOGV("NativeWindowDestroyed: %p -- %p\n", activity, window); 480 | android_app_set_window((struct android_app*)activity->instance, NULL); 481 | } 482 | 483 | static void onInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) { 484 | LOGV("InputQueueCreated: %p -- %p\n", activity, queue); 485 | android_app_set_input((struct android_app*)activity->instance, queue); 486 | } 487 | 488 | static void onInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) { 489 | LOGV("InputQueueDestroyed: %p -- %p\n", activity, queue); 490 | android_app_set_input((struct android_app*)activity->instance, NULL); 491 | } 492 | 493 | static void onNativeWindowRedrawNeeded(ANativeActivity* activity, ANativeWindow *window ) { 494 | LOGV("onNativeWindowRedrawNeeded: %p -- %p\n", activity, window); 495 | } 496 | 497 | JNIEXPORT 498 | void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState, 499 | size_t savedStateSize) { 500 | LOGV("Creating: %p\n", activity); 501 | activity->callbacks->onDestroy = onDestroy; 502 | activity->callbacks->onStart = onStart; 503 | activity->callbacks->onResume = onResume; 504 | activity->callbacks->onSaveInstanceState = onSaveInstanceState; 505 | activity->callbacks->onPause = onPause; 506 | activity->callbacks->onStop = onStop; 507 | activity->callbacks->onConfigurationChanged = onConfigurationChanged; 508 | activity->callbacks->onLowMemory = onLowMemory; 509 | activity->callbacks->onWindowFocusChanged = onWindowFocusChanged; 510 | activity->callbacks->onNativeWindowCreated = onNativeWindowCreated; 511 | activity->callbacks->onNativeWindowDestroyed = onNativeWindowDestroyed; 512 | activity->callbacks->onInputQueueCreated = onInputQueueCreated; 513 | activity->callbacks->onInputQueueDestroyed = onInputQueueDestroyed; 514 | activity->callbacks->onNativeWindowRedrawNeeded = onNativeWindowRedrawNeeded; 515 | 516 | activity->instance = android_app_create(activity, savedState, savedStateSize); 517 | } 518 | 519 | void RunCallbackOnUIThread( void (*callback)(void *), void * opaque ) 520 | { 521 | MainThreadCallbackProps gpdata; 522 | gpdata.callback = callback; 523 | gpdata.opaque = opaque; 524 | write(gapp->uimsgwrite, &gpdata, sizeof(gpdata) ); 525 | } 526 | 527 | -------------------------------------------------------------------------------- /test.c: -------------------------------------------------------------------------------- 1 | //Copyright (c) 2011-2020 <>< Charles Lohr - Under the MIT/x11 or NewBSD License you choose. 2 | //Copyright (c) 2024-2025 EmmanuelMess - Under the MIT/x11 or NewBSD License you choose. 3 | // NO WARRANTY! NO GUARANTEE OF SUPPORT! USE AT YOUR OWN RISK 4 | 5 | #include "os_generic.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // Add clay for UI 14 | #define CLAY_IMPLEMENTATION 15 | #include "clay.h" 16 | #include "clay_renderer_cnfg.c" 17 | 18 | int lastbuttonx = 0; 19 | int lastbuttony = 0; 20 | int lastmotionx = 0; 21 | int lastmotiony = 0; 22 | int lastbid = 0; 23 | int lastmask = 0; 24 | int lastkey, lastkeydown; 25 | int deltamotionx = 0; 26 | int deltamotiony = 0; 27 | 28 | uint8_t buttonstate[8]; 29 | 30 | void HandleKey( int keycode, int bDown ) 31 | { 32 | lastkey = keycode; 33 | lastkeydown = bDown; 34 | 35 | if( keycode == 4 ) { AndroidSendToBack( 1 ); } //Handle Physical Back Button. 36 | } 37 | 38 | void HandleButton( int x, int y, int button, int bDown ) 39 | { 40 | buttonstate[button] = bDown; 41 | lastbid = button; 42 | lastbuttonx = x; 43 | lastbuttony = y; 44 | deltamotionx = 0; 45 | deltamotiony = 0; 46 | } 47 | 48 | void HandleMotion( int x, int y, int mask ) 49 | { 50 | lastmask = mask; 51 | deltamotionx = x - lastmotionx; 52 | deltamotiony = y - lastmotiony; 53 | lastmotionx = x; 54 | lastmotiony = y; 55 | } 56 | 57 | short screenx, screeny; 58 | 59 | extern struct android_app * gapp; 60 | 61 | int HandleDestroy() 62 | { 63 | printf( "Destroying\n" ); 64 | return 0; 65 | } 66 | 67 | volatile int suspended; 68 | 69 | void HandleSuspend() 70 | { 71 | suspended = 1; 72 | } 73 | 74 | void HandleResume() 75 | { 76 | suspended = 0; 77 | } 78 | 79 | void HandleThisWindowTermination() 80 | { 81 | suspended = 1; 82 | } 83 | 84 | #define COLOR_ORANGE (Clay_Color) {225, 138, 50, 255} 85 | #define COLOR_BLUE (Clay_Color) {111, 173, 162, 255} 86 | 87 | Image profilePicture = {}; 88 | 89 | Clay_String profileText = CLAY_STRING("Profile Page one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen"); 90 | Clay_TextElementConfig headerTextConfig = (Clay_TextElementConfig) { .fontId = 1, .fontSize = 16, .textColor = {0,0,0,255} }; 91 | 92 | void HandleHeaderButtonInteraction(Clay_ElementId elementId, Clay_PointerData pointerData, intptr_t userData) { 93 | if (pointerData.state == CLAY_POINTER_DATA_PRESSED_THIS_FRAME) { 94 | // Do some click handling 95 | } 96 | } 97 | 98 | // Examples of re-usable "Components" 99 | void RenderHeaderButton(Clay_String text) { 100 | CLAY(CLAY_LAYOUT({ .padding = {16, 8} }), 101 | CLAY_RECTANGLE({ .color = Clay_Hovered() ? COLOR_BLUE : COLOR_ORANGE }), 102 | Clay_OnHover(HandleHeaderButtonInteraction, 1)) { 103 | CLAY_TEXT(text, CLAY_TEXT_CONFIG(headerTextConfig)); 104 | } 105 | } 106 | 107 | Clay_LayoutConfig dropdownTextItemLayout = (Clay_LayoutConfig) { .padding = {8, 4} }; 108 | Clay_RectangleElementConfig dropdownRectangleConfig = (Clay_RectangleElementConfig) { .color = {180, 180, 180, 255} }; 109 | Clay_TextElementConfig dropdownTextElementConfig = (Clay_TextElementConfig) { .fontSize = 24, .textColor = {255,255,255,255} }; 110 | 111 | void RenderDropdownTextItem(int index) { 112 | CLAY(CLAY_IDI("ScrollContainerItem", index), CLAY_LAYOUT(dropdownTextItemLayout), CLAY_RECTANGLE(dropdownRectangleConfig)) { 113 | CLAY_TEXT(CLAY_STRING("I'm a text field in a scroll container."), &dropdownTextElementConfig); 114 | } 115 | } 116 | 117 | Clay_RenderCommandArray CreateLayout() { 118 | Clay_BeginLayout(); 119 | CLAY(CLAY_ID("OuterContainer"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() }, .padding = { 16, 16 }, .childGap = 16 }), CLAY_RECTANGLE({ .color = {200, 200, 200, 255} })) { 120 | CLAY(CLAY_ID("SideBar"), CLAY_LAYOUT({ .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_GROW() }, .padding = {16, 16}, .childGap = 16 }), CLAY_RECTANGLE({ .color = {150, 150, 255, 255} })) { 121 | CLAY(CLAY_ID("ProfilePictureOuter"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_GROW() }, .padding = { 8, 8 }, .childGap = 8, .childAlignment = { .y = CLAY_ALIGN_Y_CENTER } }), CLAY_RECTANGLE({ .color = {130, 130, 255, 255} })) { 122 | CLAY(CLAY_ID("ProfilePicture"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) } }), CLAY_IMAGE({ .imageData = &profilePicture, .sourceDimensions = {60, 60} })) {} 123 | CLAY_TEXT(profileText, CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {0, 0, 0, 255} })); 124 | } 125 | CLAY(CLAY_ID("SidebarBlob1"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(50) }}), CLAY_RECTANGLE({ .color = {110, 110, 255, 255} })) {} 126 | CLAY(CLAY_ID("SidebarBlob2"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(50) }}), CLAY_RECTANGLE({ .color = {110, 110, 255, 255} })) {} 127 | CLAY(CLAY_ID("SidebarBlob3"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(50) }}), CLAY_RECTANGLE({ .color = {110, 110, 255, 255} })) {} 128 | CLAY(CLAY_ID("SidebarBlob4"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(50) }}), CLAY_RECTANGLE({ .color = {110, 110, 255, 255} })) {} 129 | } 130 | 131 | CLAY(CLAY_ID("RightPanel"), CLAY_LAYOUT({ .layoutDirection = CLAY_TOP_TO_BOTTOM, .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() }, .childGap = 16 })) { 132 | CLAY(CLAY_ID("HeaderBar"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_GROW() }, .childAlignment = { .x = CLAY_ALIGN_X_RIGHT }, .padding = {8, 8}, .childGap = 8 }), CLAY_RECTANGLE({ .color = {180, 180, 180, 255} })) { 133 | RenderHeaderButton(CLAY_STRING("Header Item 1")); 134 | RenderHeaderButton(CLAY_STRING("Header Item 2")); 135 | RenderHeaderButton(CLAY_STRING("Header Item 3")); 136 | } 137 | CLAY(CLAY_ID("MainContent"), 138 | CLAY_SCROLL({ .vertical = true }), 139 | CLAY_LAYOUT({ .layoutDirection = CLAY_TOP_TO_BOTTOM, .padding = {16, 16}, .childGap = 16, .sizing = { CLAY_SIZING_GROW() } }), 140 | CLAY_RECTANGLE({ .color = {200, 200, 255, 255} })) 141 | { 142 | CLAY(CLAY_ID("FloatingContainer"), 143 | CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_FIXED(300) }, .padding = { 16, 16 }}), 144 | CLAY_FLOATING({ .zIndex = 1, .attachment = { CLAY_ATTACH_POINT_CENTER_TOP, CLAY_ATTACH_POINT_CENTER_TOP }, .offset = {0, -16} }), 145 | CLAY_BORDER_OUTSIDE({ .color = {80, 80, 80, 255}, .width = 2 }), 146 | CLAY_RECTANGLE({ .color = {140,80, 200, 200 }}) 147 | ) { 148 | CLAY_TEXT(CLAY_STRING("I'm an inline floating container."), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {255,255,255,255} })); 149 | } 150 | 151 | CLAY_TEXT(CLAY_STRING("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt."), 152 | CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {0,0,0,255} })); 153 | 154 | CLAY(CLAY_ID("Photos2"), CLAY_LAYOUT({ .childGap = 16, .padding = { 16, 16 }}), CLAY_RECTANGLE({ .color = {180, 180, 220, 255} })) { 155 | CLAY(CLAY_ID("Picture4"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}), CLAY_IMAGE({ .imageData = &profilePicture, .sourceDimensions = {120, 120} })) {} 156 | CLAY(CLAY_ID("Picture5"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}), CLAY_IMAGE({ .imageData = &profilePicture, .sourceDimensions = {120, 120} })) {} 157 | CLAY(CLAY_ID("Picture6"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}), CLAY_IMAGE({ .imageData = &profilePicture, .sourceDimensions = {120, 120} })) {} 158 | } 159 | 160 | CLAY_TEXT(CLAY_STRING("Faucibus purus in massa tempor nec. Nec ullamcorper sit amet risus nullam eget felis eget nunc. Diam vulputate ut pharetra sit amet aliquam id diam. Lacus suspendisse faucibus interdum posuere lorem. A diam sollicitudin tempor id. Amet massa vitae tortor condimentum lacinia. Aliquet nibh praesent tristique magna."), 161 | CLAY_TEXT_CONFIG({ .fontSize = 24, .lineHeight = 60, .textColor = {0,0,0,255} })); 162 | 163 | CLAY_TEXT(CLAY_STRING("Suspendisse in est ante in nibh. Amet venenatis urna cursus eget nunc scelerisque viverra. Elementum sagittis vitae et leo duis ut diam quam nulla. Enim nulla aliquet porttitor lacus. Pellentesque habitant morbi tristique senectus et. Facilisi nullam vehicula ipsum a arcu cursus vitae.\nSem fringilla ut morbi tincidunt. Euismod quis viverra nibh cras pulvinar mattis nunc sed. Velit sed ullamcorper morbi tincidunt ornare massa. Varius quam quisque id diam vel quam. Nulla pellentesque dignissim enim sit amet venenatis. Enim lobortis scelerisque fermentum dui faucibus in. Pretium viverra suspendisse potenti nullam ac tortor vitae. Lectus vestibulum mattis ullamcorper velit sed. Eget mauris pharetra et ultrices neque ornare aenean euismod elementum. Habitant morbi tristique senectus et. Integer vitae justo eget magna fermentum iaculis eu. Semper quis lectus nulla at volutpat diam. Enim praesent elementum facilisis leo. Massa vitae tortor condimentum lacinia quis vel."), 164 | CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {0,0,0,255} })); 165 | 166 | CLAY(CLAY_ID("Photos"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_GROW() }, .childAlignment = { .x = CLAY_ALIGN_X_CENTER, .y = CLAY_ALIGN_Y_CENTER }, .childGap = 16, .padding = {16, 16} }), CLAY_RECTANGLE({ .color = {180, 180, 220, 255} })) { 167 | CLAY(CLAY_ID("Picture2"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}), CLAY_IMAGE({ .imageData = &profilePicture, .sourceDimensions = {120, 120} })) {} 168 | CLAY(CLAY_ID("Picture1"), CLAY_LAYOUT({ .childAlignment = { .x = CLAY_ALIGN_X_CENTER }, .layoutDirection = CLAY_TOP_TO_BOTTOM, .padding = {8, 8} }), CLAY_RECTANGLE({ .color = {170, 170, 220, 255} })) { 169 | CLAY(CLAY_ID("ProfilePicture2"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_FIXED(60), .height = CLAY_SIZING_FIXED(60) }}), CLAY_IMAGE({ .imageData = &profilePicture, .sourceDimensions = {60, 60} })) {} 170 | CLAY_TEXT(CLAY_STRING("Image caption below"), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {0,0,0,255} })); 171 | } 172 | CLAY(CLAY_ID("Picture3"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_FIXED(120), .height = CLAY_SIZING_FIXED(120) }}), CLAY_IMAGE({ .imageData = &profilePicture, .sourceDimensions = {120, 120} })) {} 173 | } 174 | 175 | CLAY_TEXT(CLAY_STRING("Amet cursus sit amet dictum sit amet justo donec. Et malesuada fames ac turpis egestas maecenas. A lacus vestibulum sed arcu non odio euismod lacinia. Gravida neque convallis a cras. Dui nunc mattis enim ut tellus elementum sagittis vitae et. Orci sagittis eu volutpat odio facilisis mauris. Neque gravida in fermentum et sollicitudin ac orci. Ultrices dui sapien eget mi proin sed libero. Euismod quis viverra nibh cras pulvinar mattis. Diam volutpat commodo sed egestas egestas. In fermentum posuere urna nec tincidunt praesent semper. Integer eget aliquet nibh praesent tristique magna.\nId cursus metus aliquam eleifend mi in. Sed pulvinar proin gravida hendrerit lectus a. Etiam tempor orci eu lobortis elementum nibh tellus. Nullam vehicula ipsum a arcu cursus vitae. Elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus. Condimentum lacinia quis vel eros donec ac odio. Mattis pellentesque id nibh tortor id aliquet lectus. Turpis egestas integer eget aliquet nibh praesent tristique. Porttitor massa id neque aliquam vestibulum morbi. Mauris commodo quis imperdiet massa tincidunt nunc pulvinar sapien et. Nunc scelerisque viverra mauris in aliquam sem fringilla. Suspendisse ultrices gravida dictum fusce ut placerat orci nulla.\nLacus laoreet non curabitur gravida arcu ac tortor dignissim. Urna nec tincidunt praesent semper feugiat nibh sed pulvinar. Tristique senectus et netus et malesuada fames ac. Nunc aliquet bibendum enim facilisis gravida. Egestas maecenas pharetra convallis posuere morbi leo urna molestie. Sapien nec sagittis aliquam malesuada bibendum arcu vitae elementum curabitur. Ac turpis egestas maecenas pharetra convallis posuere morbi leo urna. Viverra vitae congue eu consequat. Aliquet enim tortor at auctor urna. Ornare massa eget egestas purus viverra accumsan in nisl nisi. Elit pellentesque habitant morbi tristique senectus et netus et malesuada.\nSuspendisse ultrices gravida dictum fusce ut placerat orci nulla pellentesque. Lobortis feugiat vivamus at augue eget arcu. Vitae justo eget magna fermentum iaculis eu. Gravida rutrum quisque non tellus orci. Ipsum faucibus vitae aliquet nec. Nullam non nisi est sit amet. Nunc consequat interdum varius sit amet mattis vulputate enim. Sem fringilla ut morbi tincidunt augue interdum. Vitae purus faucibus ornare suspendisse. Massa tincidunt nunc pulvinar sapien et. Fringilla ut morbi tincidunt augue interdum velit euismod in. Donec massa sapien faucibus et. Est placerat in egestas erat imperdiet. Gravida rutrum quisque non tellus. Morbi non arcu risus quis varius quam quisque id diam. Habitant morbi tristique senectus et netus et malesuada fames ac. Eget lorem dolor sed viverra.\nOrnare massa eget egestas purus viverra. Varius vel pharetra vel turpis nunc eget lorem. Consectetur purus ut faucibus pulvinar elementum. Placerat in egestas erat imperdiet sed euismod nisi. Interdum velit euismod in pellentesque massa placerat duis ultricies lacus. Aliquam nulla facilisi cras fermentum odio eu. Est pellentesque elit ullamcorper dignissim cras tincidunt. Nunc sed id semper risus in hendrerit gravida rutrum. A pellentesque sit amet porttitor eget dolor morbi. Pellentesque habitant morbi tristique senectus et netus et malesuada fames. Nisl nunc mi ipsum faucibus vitae aliquet nec ullamcorper. Sed id semper risus in hendrerit gravida. Tincidunt praesent semper feugiat nibh. Aliquet lectus proin nibh nisl condimentum id venenatis a. Enim sit amet venenatis urna cursus eget. In egestas erat imperdiet sed euismod nisi porta lorem mollis. Lacinia quis vel eros donec ac odio tempor orci. Donec pretium vulputate sapien nec sagittis aliquam malesuada bibendum arcu. Erat pellentesque adipiscing commodo elit at.\nEgestas sed sed risus pretium quam vulputate. Vitae congue mauris rhoncus aenean vel elit scelerisque mauris pellentesque. Aliquam malesuada bibendum arcu vitae elementum. Congue mauris rhoncus aenean vel elit scelerisque mauris. Pellentesque dignissim enim sit amet venenatis urna cursus. Et malesuada fames ac turpis egestas sed tempus urna. Vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Nibh cras pulvinar mattis nunc sed blandit libero. Fringilla est ullamcorper eget nulla facilisi etiam dignissim. Aenean euismod elementum nisi quis eleifend quam adipiscing vitae proin. Mauris pharetra et ultrices neque ornare aenean euismod elementum. Ornare quam viverra orci sagittis eu. Odio ut sem nulla pharetra diam sit amet nisl suscipit. Ornare lectus sit amet est. Ullamcorper sit amet risus nullam eget. Tincidunt lobortis feugiat vivamus at augue eget arcu dictum.\nUrna nec tincidunt praesent semper feugiat nibh. Ut venenatis tellus in metus vulputate eu scelerisque felis. Cursus risus at ultrices mi tempus. In pellentesque massa placerat duis ultricies lacus sed turpis. Platea dictumst quisque sagittis purus. Cras adipiscing enim eu turpis egestas. Egestas sed tempus urna et pharetra pharetra. Netus et malesuada fames ac turpis egestas integer eget aliquet. Ac turpis egestas sed tempus. Sed lectus vestibulum mattis ullamcorper velit sed. Ante metus dictum at tempor commodo ullamcorper a. Augue neque gravida in fermentum et sollicitudin ac. Praesent semper feugiat nibh sed pulvinar proin gravida. Metus aliquam eleifend mi in nulla posuere sollicitudin aliquam ultrices. Neque gravida in fermentum et sollicitudin ac orci phasellus egestas.\nRidiculus mus mauris vitae ultricies. Morbi quis commodo odio aenean. Duis ultricies lacus sed turpis. Non pulvinar neque laoreet suspendisse interdum consectetur. Scelerisque eleifend donec pretium vulputate sapien nec sagittis aliquam. Volutpat est velit egestas dui id ornare arcu odio ut. Viverra tellus in hac habitasse platea dictumst vestibulum rhoncus est. Vestibulum lectus mauris ultrices eros. Sed blandit libero volutpat sed cras ornare. Id leo in vitae turpis massa sed elementum tempus. Gravida dictum fusce ut placerat orci nulla pellentesque. Pretium quam vulputate dignissim suspendisse in. Nisl suscipit adipiscing bibendum est ultricies integer quis auctor. Risus viverra adipiscing at in tellus. Turpis nunc eget lorem dolor sed viverra ipsum. Senectus et netus et malesuada fames ac. Habitasse platea dictumst vestibulum rhoncus est. Nunc sed id semper risus in hendrerit gravida. Felis eget velit aliquet sagittis id. Eget felis eget nunc lobortis.\nMaecenas pharetra convallis posuere morbi leo. Maecenas volutpat blandit aliquam etiam. A condimentum vitae sapien pellentesque habitant morbi tristique senectus et. Pulvinar mattis nunc sed blandit libero volutpat sed. Feugiat in ante metus dictum at tempor commodo ullamcorper. Vel pharetra vel turpis nunc eget lorem dolor. Est placerat in egestas erat imperdiet sed euismod. Quisque non tellus orci ac auctor augue mauris augue. Placerat vestibulum lectus mauris ultrices eros in cursus turpis. Enim nunc faucibus a pellentesque sit. Adipiscing vitae proin sagittis nisl. Iaculis at erat pellentesque adipiscing commodo elit at imperdiet. Aliquam sem fringilla ut morbi.\nArcu odio ut sem nulla pharetra diam sit amet nisl. Non diam phasellus vestibulum lorem sed. At erat pellentesque adipiscing commodo elit at. Lacus luctus accumsan tortor posuere ac ut consequat. Et malesuada fames ac turpis egestas integer. Tristique magna sit amet purus. A condimentum vitae sapien pellentesque habitant. Quis varius quam quisque id diam vel quam. Est ullamcorper eget nulla facilisi etiam dignissim diam quis. Augue interdum velit euismod in pellentesque massa. Elit scelerisque mauris pellentesque pulvinar pellentesque habitant. Vulputate eu scelerisque felis imperdiet. Nibh tellus molestie nunc non blandit massa. Velit euismod in pellentesque massa placerat. Sed cras ornare arcu dui. Ut sem viverra aliquet eget sit. Eu lobortis elementum nibh tellus molestie nunc non. Blandit libero volutpat sed cras ornare arcu dui vivamus.\nSit amet aliquam id diam maecenas. Amet risus nullam eget felis eget nunc lobortis mattis aliquam. Magna sit amet purus gravida. Egestas purus viverra accumsan in nisl nisi. Leo duis ut diam quam. Ante metus dictum at tempor commodo ullamcorper. Ac turpis egestas integer eget. Fames ac turpis egestas integer eget aliquet nibh. Sem integer vitae justo eget magna fermentum. Semper auctor neque vitae tempus quam pellentesque nec nam aliquam. Vestibulum mattis ullamcorper velit sed. Consectetur adipiscing elit duis tristique sollicitudin nibh. Massa id neque aliquam vestibulum morbi blandit cursus risus.\nCursus sit amet dictum sit amet justo donec enim diam. Egestas erat imperdiet sed euismod. Nullam vehicula ipsum a arcu cursus vitae congue mauris. Habitasse platea dictumst vestibulum rhoncus est pellentesque elit. Duis ultricies lacus sed turpis tincidunt id aliquet risus feugiat. Faucibus ornare suspendisse sed nisi lacus sed viverra. Pretium fusce id velit ut tortor pretium viverra. Fermentum odio eu feugiat pretium nibh ipsum consequat nisl vel. Senectus et netus et malesuada. Tellus pellentesque eu tincidunt tortor aliquam. Aenean sed adipiscing diam donec adipiscing tristique risus nec feugiat. Quis vel eros donec ac odio. Id interdum velit laoreet id donec ultrices tincidunt.\nMassa id neque aliquam vestibulum morbi blandit cursus risus at. Enim tortor at auctor urna nunc id cursus metus. Lorem ipsum dolor sit amet consectetur. At quis risus sed vulputate odio. Facilisis mauris sit amet massa vitae tortor condimentum lacinia quis. Et malesuada fames ac turpis egestas maecenas. Bibendum arcu vitae elementum curabitur vitae nunc sed velit dignissim. Viverra orci sagittis eu volutpat odio facilisis mauris. Adipiscing bibendum est ultricies integer quis auctor elit sed. Neque viverra justo nec ultrices dui sapien. Elementum nibh tellus molestie nunc non blandit massa enim. Euismod elementum nisi quis eleifend quam adipiscing vitae proin sagittis. Faucibus ornare suspendisse sed nisi. Quis viverra nibh cras pulvinar mattis nunc sed blandit. Tristique senectus et netus et. Magnis dis parturient montes nascetur ridiculus mus.\nDolor magna eget est lorem ipsum dolor. Nibh sit amet commodo nulla. Donec pretium vulputate sapien nec sagittis aliquam malesuada. Cras adipiscing enim eu turpis egestas pretium. Cras ornare arcu dui vivamus arcu felis bibendum ut tristique. Mus mauris vitae ultricies leo integer. In nulla posuere sollicitudin aliquam ultrices sagittis orci. Quis hendrerit dolor magna eget. Nisl tincidunt eget nullam non. Vitae congue eu consequat ac felis donec et odio. Vivamus at augue eget arcu dictum varius duis at. Ornare quam viverra orci sagittis.\nErat nam at lectus urna duis convallis. Massa placerat duis ultricies lacus sed turpis tincidunt id aliquet. Est ullamcorper eget nulla facilisi etiam dignissim diam. Arcu vitae elementum curabitur vitae nunc sed velit dignissim sodales. Tortor vitae purus faucibus ornare suspendisse sed nisi lacus. Neque viverra justo nec ultrices dui sapien eget mi proin. Viverra accumsan in nisl nisi scelerisque eu ultrices. Consequat interdum varius sit amet mattis. In aliquam sem fringilla ut morbi. Eget arcu dictum varius duis at. Nulla aliquet porttitor lacus luctus accumsan tortor posuere. Arcu bibendum at varius vel pharetra vel turpis. Hac habitasse platea dictumst quisque sagittis purus sit amet. Sapien eget mi proin sed libero enim sed. Quam elementum pulvinar etiam non quam lacus suspendisse faucibus interdum. Semper viverra nam libero justo. Fusce ut placerat orci nulla pellentesque dignissim enim sit amet. Et malesuada fames ac turpis egestas maecenas pharetra convallis posuere.\nTurpis egestas sed tempus urna et pharetra pharetra massa. Gravida in fermentum et sollicitudin ac orci phasellus. Ornare suspendisse sed nisi lacus sed viverra tellus in. Fames ac turpis egestas maecenas pharetra convallis posuere. Mi proin sed libero enim sed faucibus turpis. Sit amet mauris commodo quis imperdiet massa tincidunt nunc. Ut etiam sit amet nisl purus in mollis nunc. Habitasse platea dictumst quisque sagittis purus sit amet volutpat consequat. Eget aliquet nibh praesent tristique magna. Sit amet est placerat in egestas erat. Commodo sed egestas egestas fringilla. Enim nulla aliquet porttitor lacus luctus accumsan tortor posuere ac. Et molestie ac feugiat sed lectus vestibulum mattis ullamcorper. Dignissim convallis aenean et tortor at risus viverra. Morbi blandit cursus risus at ultrices mi. Ac turpis egestas integer eget aliquet nibh praesent tristique magna.\nVolutpat sed cras ornare arcu dui. Egestas erat imperdiet sed euismod nisi porta lorem mollis aliquam. Viverra justo nec ultrices dui sapien. Amet risus nullam eget felis eget nunc lobortis. Metus aliquam eleifend mi in. Ut eu sem integer vitae. Auctor elit sed vulputate mi sit amet. Nisl nisi scelerisque eu ultrices. Dictum fusce ut placerat orci nulla. Pellentesque habitant morbi tristique senectus et. Auctor elit sed vulputate mi sit. Tincidunt arcu non sodales neque. Mi in nulla posuere sollicitudin aliquam. Morbi non arcu risus quis varius quam quisque id diam. Cras adipiscing enim eu turpis egestas pretium aenean pharetra magna. At auctor urna nunc id cursus metus aliquam. Mauris a diam maecenas sed enim ut sem viverra. Nunc scelerisque viverra mauris in. In iaculis nunc sed augue lacus viverra vitae congue eu. Volutpat blandit aliquam etiam erat velit scelerisque in dictum non."), 176 | CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {0,0,0,255} })); 177 | } 178 | } 179 | 180 | CLAY(CLAY_ID("Blob4Floating2"), CLAY_FLOATING({ .zIndex = 1, .parentId = Clay_GetElementId(CLAY_STRING("SidebarBlob4")).id })) { 181 | CLAY(CLAY_ID("ScrollContainer"), CLAY_LAYOUT({ .sizing = { .height = CLAY_SIZING_FIXED(200) }, .childGap = 2 }), CLAY_SCROLL({ .vertical = true })) { 182 | CLAY(CLAY_ID("FloatingContainer2"), CLAY_LAYOUT({ }), CLAY_FLOATING({ .zIndex = 1 })) { 183 | CLAY(CLAY_ID("FloatingContainerInner"), CLAY_LAYOUT({ .sizing = { .width = CLAY_SIZING_FIXED(300), .height = CLAY_SIZING_FIXED(300) }, .padding = {16, 16} }), CLAY_RECTANGLE({ .color = {140,80, 200, 200} })) { 184 | CLAY_TEXT(CLAY_STRING("I'm an inline floating container."), CLAY_TEXT_CONFIG({ .fontSize = 24, .textColor = {255,255,255,255} })); 185 | } 186 | } 187 | CLAY(CLAY_ID("ScrollContainerInner"), CLAY_LAYOUT({ .layoutDirection = CLAY_TOP_TO_BOTTOM }), CLAY_RECTANGLE({ .color = {160, 160, 160, 255} })) { 188 | for (int i = 0; i < 100; i++) { 189 | RenderDropdownTextItem(i); 190 | } 191 | } 192 | } 193 | } 194 | Clay_ScrollContainerData scrollData = Clay_GetScrollContainerData(Clay_GetElementId(CLAY_STRING("MainContent"))); 195 | if (scrollData.found) { 196 | CLAY(CLAY_ID("ScrollBar"), 197 | CLAY_FLOATING({ 198 | .offset = { .y = -(scrollData.scrollPosition->y / scrollData.contentDimensions.height) * scrollData.scrollContainerDimensions.height }, 199 | .zIndex = 1, 200 | .parentId = Clay_GetElementId(CLAY_STRING("MainContent")).id, 201 | .attachment = {.element = CLAY_ATTACH_POINT_RIGHT_TOP, .parent = CLAY_ATTACH_POINT_RIGHT_TOP} 202 | }) 203 | ) { 204 | CLAY(CLAY_ID("ScrollBarButton"), 205 | CLAY_LAYOUT({ .sizing = {CLAY_SIZING_FIXED(12), CLAY_SIZING_FIXED((scrollData.scrollContainerDimensions.height / scrollData.contentDimensions.height) * scrollData.scrollContainerDimensions.height) }}), 206 | CLAY_RECTANGLE({ .cornerRadius = {6}, .color = Clay_PointerOver(Clay__HashString(CLAY_STRING("ScrollBar"), 0, 0)) ? (Clay_Color){100, 100, 140, 150} : (Clay_Color){120, 120, 160, 150} }) 207 | ) {} 208 | } 209 | } 210 | } 211 | return Clay_EndLayout(); 212 | } 213 | 214 | typedef struct 215 | { 216 | Clay_Vector2 clickOrigin; 217 | Clay_Vector2 positionOrigin; 218 | bool mouseDown; 219 | } ScrollbarData; 220 | 221 | ScrollbarData scrollbarData = (ScrollbarData) {}; 222 | 223 | bool debugEnabled = false; 224 | 225 | void UpdateDrawFrame(short screenx, short screeny, float frameTime) 226 | { 227 | float mouseWheelX = deltamotionx; 228 | float mouseWheelY = deltamotiony; 229 | 230 | //---------------------------------------------------------------------------------- 231 | // Handle scroll containers 232 | Clay_Vector2 mousePosition = (Clay_Vector2) { .x = lastmotionx, .y = lastmotiony }; 233 | Clay_SetPointerState(mousePosition, buttonstate[0] && !scrollbarData.mouseDown); 234 | Clay_SetLayoutDimensions((Clay_Dimensions) { (float)screenx, (float)screeny }); 235 | if (!buttonstate[0]) { 236 | scrollbarData.mouseDown = false; 237 | } 238 | 239 | if (buttonstate[0] && !scrollbarData.mouseDown && Clay_PointerOver(Clay__HashString(CLAY_STRING("ScrollBar"), 0, 0))) { 240 | Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay__HashString(CLAY_STRING("MainContent"), 0, 0)); 241 | scrollbarData.clickOrigin = mousePosition; 242 | scrollbarData.positionOrigin = *scrollContainerData.scrollPosition; 243 | scrollbarData.mouseDown = true; 244 | } else if (scrollbarData.mouseDown) { 245 | Clay_ScrollContainerData scrollContainerData = Clay_GetScrollContainerData(Clay__HashString(CLAY_STRING("MainContent"), 0, 0)); 246 | if (scrollContainerData.contentDimensions.height > 0) { 247 | Clay_Vector2 ratio = (Clay_Vector2) { 248 | scrollContainerData.contentDimensions.width / scrollContainerData.scrollContainerDimensions.width, 249 | scrollContainerData.contentDimensions.height / scrollContainerData.scrollContainerDimensions.height, 250 | }; 251 | if (scrollContainerData.config.vertical) { 252 | scrollContainerData.scrollPosition->y = scrollbarData.positionOrigin.y + (scrollbarData.clickOrigin.y - mousePosition.y) * ratio.y; 253 | } 254 | if (scrollContainerData.config.horizontal) { 255 | scrollContainerData.scrollPosition->x = scrollbarData.positionOrigin.x + (scrollbarData.clickOrigin.x - mousePosition.x) * ratio.x; 256 | } 257 | } 258 | } 259 | 260 | Clay_UpdateScrollContainers(true, (Clay_Vector2) {mouseWheelX, mouseWheelY}, frameTime); 261 | // Generate the auto layout for rendering 262 | double currentTime = OGGetAbsoluteTime(); 263 | Clay_RenderCommandArray renderCommands = CreateLayout(); 264 | printf("layout time: %f microseconds\n", (OGGetAbsoluteTime() - currentTime) * 1000 * 1000); 265 | // RENDERING --------------------------------- 266 | currentTime = OGGetAbsoluteTime(); 267 | CNFGClearFrame(); 268 | CNFGColor( 0xFFFFFFFF ); 269 | CNFGPenX = 0; CNFGPenY = 0; 270 | Clay_CNFG_Render(screenx, screeny, renderCommands); 271 | //On Android, CNFGSwapBuffers must be called, and CNFGUpdateScreenWithBitmap does not have an implied framebuffer swap. 272 | CNFGSwapBuffers(); 273 | 274 | printf("render time: %f ms\n", (OGGetAbsoluteTime() - currentTime) * 1000); 275 | 276 | //---------------------------------------------------------------------------------- 277 | } 278 | 279 | int main( int argc, char ** argv ) 280 | { 281 | double ThisTime = OGGetAbsoluteTime(); 282 | 283 | printf( "Starting Up" ); 284 | 285 | CNFGSetupFullscreen( "Test Bench", 0 ); 286 | 287 | HandleWindowTermination = HandleThisWindowTermination; 288 | 289 | AAsset * profilePictureAsset = AAssetManager_open( gapp->activity->assetManager, "profile-picture.bmp", AASSET_MODE_BUFFER ); 290 | if( profilePictureAsset ) 291 | { 292 | size_t fileLength = AAsset_getLength(profilePictureAsset); 293 | const char * buffer = AAsset_getBuffer( profilePictureAsset ); 294 | 295 | uint32_t * imageMemory = (uint32_t*)malloc( fileLength ); 296 | uint64_t imageMemoryPosition = (fileLength - 54) / 4; 297 | 298 | for (uint64_t i = 54; i < fileLength; i += 4, imageMemoryPosition--) { 299 | uint8_t a = buffer[i+0]; 300 | uint8_t r = buffer[i+1]; 301 | uint8_t g = buffer[i+2]; 302 | uint8_t b = buffer[i+3]; 303 | 304 | imageMemory[imageMemoryPosition] = (r << 24) + (g << 16) + (b << 8) + a; 305 | } 306 | 307 | profilePicture.width = (buffer[21] << 24) + (buffer[20] << 16) + (buffer[19] << 8) + buffer[18]; 308 | profilePicture.height = (buffer[25] << 24) + (buffer[24] << 16) + (buffer[23] << 8) + buffer[22]; 309 | profilePicture.data = imageMemory; 310 | } 311 | 312 | uint64_t totalMemorySize = Clay_MinMemorySize(); 313 | Clay_Arena clayMemory = (Clay_Arena) { .label = CLAY_STRING("Clay Memory Arena"), .capacity = totalMemorySize, .memory = (char *)malloc(totalMemorySize) }; 314 | Clay_SetMeasureTextFunction(CNFG_MeasureText); 315 | Clay_Initialize(clayMemory, (Clay_Dimensions) {0,0}); 316 | 317 | printf( "Startup Complete" ); 318 | 319 | while(1) 320 | { 321 | CNFGHandleInput(); 322 | 323 | if( suspended ) { usleep(50000); continue; } 324 | 325 | CNFGGetDimensions( &screenx, &screeny ); 326 | 327 | UpdateDrawFrame(screenx, screeny, OGGetAbsoluteTime() - ThisTime); 328 | 329 | ThisTime = OGGetAbsoluteTime(); 330 | } 331 | 332 | return(0); 333 | } 334 | 335 | --------------------------------------------------------------------------------