├── 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 | [](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 |
--------------------------------------------------------------------------------