Android Port
Current compiling instructions are here: Compiling for Android
Contents
FreeBASIC Android Port TODO[edit]
The FB android port is almost ready to be merged upstream, aside from some cleanup, testing, and better string<-->wstring conversion code.
libfb[edit]
- Run the testcases!
- Have another look at the DISABLE_NCURSES code (rename to DISABLE_TERMINFO)
- There's something weird going on with __fb_con.inited: different parts of FB seem to have different ideas about what INIT_CONSOLE means
- Try to provide better replacements for the couple string conversion functions missing from bionic libc
- Investigate whether the constructor priority in fbrt0.c can be made to work. Priorities seem to be ignored when linking libfb.a into the .so, but work for command line programs.
- Find out why the rtlib destructor code isn't run for a commandline program if a destructor priority is specified in fbrt0.c
- Try to provide implementations for the rtlib/android stub functions
- Parts of the NDK docs about differences vs. Linux, and check whether non-stable ABIs are used
Compiler[edit]
- "Add an -entry command line argument to fbc (needed to specify SDL_main instead of main)" (Huh? is this no longer needed?)
- Add mips and 64 bit arm targets. (There's some discussion about -arch on the FB forum and in the FB git commit messages)
SDL/gfx_sdl for Android TODO[edit]
- Better support for different screen resolutions. Currently always run at 320x200 (1x zoom)
- Can choose zoom to fill as much of the screen as possible, and centre, but would still leave a lot of black on a lot of phones. Should we scale up the screen with interpolation to fit? If at all possible we should do zooming using SDL's OpenGL-based (I think) scaling rather than our own blit.c routines.
- zoom-and-center and zoom-and-stretch are the obvious choices, and it might even make sense to make the existing backends respect those for fullscreen
- We seem to get the actual screen size as long as keepAspectRatio is false. Note that SDL's virtual screen is always whatever size you request in SDL_SetVideoMode. However using a virtual screen a different width than the real screen caused weird behaviour for me...
- In what situations does android send a RESIZE event (which is passed from Java to the SDL android driver)? Might we care?
- Can choose zoom to fill as much of the screen as possible, and centre, but would still leave a lot of black on a lot of phones. Should we scale up the screen with interpolation to fit? If at all possible we should do zooming using SDL's OpenGL-based (I think) scaling rather than our own blit.c routines.
- Clean up all the startup SDL configuration stuff. Ideally we want to be able to totally get rid of this by handling everything ourselves. For now it's quite useful
- In emulator with GPU acceleration on, there's garbage on screen before first frame is drawn
- Change the mouse/keyboard mapping (probably just in AndroidAppSettings.cfg). Currently this SDL port lets you map physical buttons and the 4 onscreen buttons to emulated key presses at compile time
- Invoking the onscreen keyboard (should we use the native, or create our own? Ideally would never be used in most games)
- Link the Menu button to something useful... maybe not always ESC
- The SDL virtual joystick (bottom left) ought to have a dead zone in the centre
- Generally there's no need for the apk to require permission to open sockets, as it currently does
Engine TODO[edit]
- Now that __FB_LINUX__ is not longer defined under android, check nothing broke.
- GOSUB/RETRACE is broken, but we use them so rarely in game that it doesn't make very much unplayable. Test possible fixes using the hero order/team menu in svn revision 5847
- Investigate reloadtest failures... or was it vectortest failures?
- Investigate slow downs. On my emulator and especially my phone, there seem to be long pauses between the end of .rpg upgrade and the titlescreen. Quitting is slow both on phone and desktop.
- Note that the armeabi platform doesn't have an FPU, floating point is emulated which is very slow (see below)
- Heavily scripted games are unplayable on a low end phone, probably ones like Bell of Chaos are unplayable on any phone. Will need a new script interpreter for these games
- Packaging games
- Figure out a better way to embed an RPG file other than sdl-android's download-on-first-run mechanism.
- Figure out how to change the package name, for example, com.ohrrpgce.gamename or com.authorsdomain.gamename
- Clean up our build system. I would like to let SCons handle creation of libapplication.so without using an Android.mk file.
- Support for other android architectures: armeabi, armeabi-v7a, x86, mips (done/unnecessary: Native x86 builds work. armeabi builds work on armeabi, armeabi-v7a and x86 devices. Haven't tried to create multi-arch .apks yet.)
- Have multiple .apks, or place all architectures in the same .apk? We don't need to provide armeabi-v7a (which has an FPU) if armeabi is fast enough for everyone
- A note from the Android docs: "Note: ARMv7-based Android devices running 4.0.3 or earlier install native libraries from the armeabi directory instead of the armeabi-v7a directory if both directories exist. This is because /lib/armeabi/ comes after /lib/armeabi-v7a/ in the APK. This issue is fixed from 4.0.4."
- See http://stackoverflow.com/questions/7080525/why-use-armeabi-v7a-code-over-armeabi-code for some info about this. There is a way to make the FPU work on armeabi for hardware that supports it (a compiler flag), although that still isn't as fast as armeabi-v7a
- The only routines we have which is explicitly floating point heavy (declares float variables) is frame_dissolved, ellipse and createminimap, plus anything that uses lots of random numbers (probably no other routines aside from those). frame_dissolved is really slow even on desktop. We could isolate those into a separate .so file to be compiled for either armeabi or armeabi-v7a (and even armeabi+VFP) while providing only armeabi build for the rest of the engine
- Have multiple .apks, or place all architectures in the same .apk? We don't need to provide armeabi-v7a (which has an FPU) if armeabi is fast enough for everyone
- Figure out how to switch between release and debug builds easily. Currently there are build options spread all over the place
- Support for other android architectures: armeabi, armeabi-v7a, x86, mips (done/unnecessary: Native x86 builds work. armeabi builds work on armeabi, armeabi-v7a and x86 devices. Haven't tried to create multi-arch .apks yet.)
- Plan for improved mouse support and Plan for improved joystick support
- However, the mouse isn't suitable for selecting menu items, because they are too small to hit with a finger. Instead, we could use regular clicking for clicking on tiles and battle targets, and a joystick mode (swiping) to move the cursor in menus. The SDL port already has this implemented, at the bottom left corner (I'm not sure whether it emulates a joystick or a mouse)
- We could also make use of gyroscopes and accelerometers
- Currently, real gamepads are mapped to keyboard keys using SDL_ANDROID_set_java_gamepad_keymap() but we need a way to distinguish multiple gamepads.
- Android-specific directories/file placement, sdcard awareness
- Invisible on-screen buttons, including game-specific settings (eg. z,x,c).
- Can already be toggled using wrapper functions in gfx_sdl.bas.
- Suspending, resuming and quitting correctly the app (suspend and resume seem to work, but I don't know if the game pauses)
- Currently the Home button acts normally, but Back and Menu buttons are mapped to keys. The only way to quit is to go back to titlescreen, which can be quite hard!
- Suspending/resuming works fine for me (James)
- Perhaps a welcome screen showing recently played .rpgs instead of dumping users in the hard to use file browser
- Timidity for MIDI support (should be easy)
- The instrument patches suggested by SDL_mixer are 18MB. Can we find a smaller set?
- When packaging for a single game, we could conceivably scan all MIDIs in the game for actually used instruments, and only include those. Probably only a fraction would be used.
- Could keep MIDI as an optional download, hopefully shareable between the generic game player and all packaged games
- Why is gfx_sdl printed as "gfx_sd"?
- Automatically replace inputscript with inputstringwithvirtualkeyboard
Notes[edit]
- Don't try to access files or do other complicated stuff in module level code (other than game.bas and custom.bas), it can cause crashes! (Not sure why, possibly only if an error occurs)
- libapplication.so contains the actual native code for the application. It isn't loaded until after the app starts up, which happens in a separate thread. The SDL startup configuration menu runs and is usable before libapplication.so is loaded!
- libsdl.so is actually loaded earlier though
- Use <NDK>/toolchains/arm-linux-androideabi-4.7/prebuilt/linux-x86_64/bin/arm-linux-androideabi-objdump -x and look for NEEDED lines to determine which dynamic libraries a binary requires.
- sdl-android/checkMissing.sh checks for missing symbols required by libapplication.so. Currently shows only dlopen, dlclose, dlsym.
Debugging[edit]
debug output is currently also mirrored to the adb logcat log. However calling debug before location of g_debug.txt is set is a bad idea, prefer calling external_log directly.
To debug, after building the .apk, cd to sdl-android/project/, push .apk to device and invoke ndk-gdb
adb install -r bin/MainActivity-debug.apk ndk-gdb --start --force --verbose
The program will run for a couple seconds before gdb attachs and it halts. Point gdb at the original FB sources, break on fb_End to catch runtime-detected errors and continue execution:
directory path/to/ohr/wip b fb_End c
You can interrupt execution by pressing Ctrl+C in gdb. When you do so you'll likely be looking at the wrong thread. "info threads" to look at all threads, "thread #" to switch to a thread, and "thread apply all bt" to print backtraces for all threads to help find the correct one.
Stripped libraries, including libapplication.so live in sdl-android/project/libs/armeabi/. Much more useful are the non-stripped originals, in sdl-android/project/obj/local/armeabi/
Splash Screen/Start-up[edit]
Image[edit]
The sdl splash screen can be replaced by replacing project/res/drawable/publisherlogo.png
We could probably make it configurable per-project by mimicing the symlink scheme of 'project/res/drawable/icon.png
First-time config[edit]
First-time config of screen size, gyroscope, etc, can be suppressed in AndroidAppSettings.cfg by editing FirstStartMenuOptions and setting it to a single space
FirstStartMenuOptions=' '
The default value of
FirstStartMenuOptions=
is actually translated into:
FirstStartMenuOptions='new Settings.ShowReadme(), (AppUsesMouse \&\& \! ForceRelativeMouseMode ? new Settings.DisplaySizeConfig(true) : new Settings.DummyMenu()), new Settings.OptionalDownloadConfig(true), new Settings.GyroscopeCalibration()'
Disabling the first-start java code works, but on the first load, you still see the splash screen for an instant, then it vanishes and you see the home screen for an instant, then the splash screen re-appears. This does not happen on subsequent starts
Config button at startup[edit]
The startup config button can (and probably should) be suppressed. I think leaving it enabled is only good for debugging, not for production. It can be suppressed by editing AndroidAppSettings.cfg
StartupMenuButtonTimeout=0
On-screen control images[edit]
The on-screen dpad and buttons look like sun-flares. The source-art for these are located in project/themes/Sun/
The easiest way to change these images is to edit them in-place, although there is not much obvious rhyme or reason to their naming scheme. Also, for some buttons there are separate images for the regular and the pressed state, but for other buttons the pressed state is the regular state flipped.
After you have edited them, you must run
cd project/themes/converter ./convert.sh
This might not work unless you first recompile the project/themes/converter/converter binary. You can do this by:
cd project/themes/converter rm converter make
the convert.sh script uses the converter binary to convert the theme images into files in project/res/raw which get included in the apk when you run build.sh
There are also copies of these images located in project/res/drawable and project/bin/res/drawable and it appears that at build-time the project/res/drawable/* files are copied to the project/bin/res/drawable location. These images get included in the apk, but as far as I can tell, they never actually get used.
From reading the code (much of which I do not understand!) I suspect that a previous developer was in the process of switching from one method to the other, and either did not finish the transition, or did not remove the old method.
Code of note includes:
- project/jni/sdl-1.2/src/video/android/SDL_touchscreenkeyboard.c - See Settings_nativeSetupScreenKeyboardButtons() and the other code that is called by it.
- project/java/Settings.java - See SetupTouchscreenKeyboardGraphics() which calls the aforementioned c code
How RedefineKeysScreenKb Works[edit]
Both virtual gamepad and actual gamepad buttons are mapped using RedefineKeysScreenKb in AndroidAppSettings.cfg
changeAppSettings.sh converts these into compile-time defines in the form SDL_ANDROID_SCREENKB_KEYCODE_# where the # is the index of the key 0 to 5. The default for these defines are set in project/jni/sdl-1.2/src/video/android/SDL_androidinput.h
These defines are then used in the SDL_android_init_keymap() function in project/jni/sdl-1.2/src/video/android/keymap.c to set up the keycode[] array
the SDL_android_init_keymap() function is wrapped for JNI in project/jni/sdl-1.2/src/video/androidSDL_androidinput.c and called by project/java/Settings.java using the name nativeInitKeymap
What I think we want to do is to use RedefineKeysScreenKb only to set up reasonable default keys for all ohrrpgce games, and then to have the game player manipulate SDL's keycode array to provide default button mappings for OUYA controllers (which should, in theory, be sane for most other controllers connected an android device) We can also use the same run-time button remapping method to implement the game-specific button-remapping described below.
Android metadata in rpg file[edit]
Some android-specific data about button-mapping and virtual gamepad display should be stored in the RPG file. This has already been mostly implemented. See: general.reld
Also relevant: Plan for platform-specific button names
Data Files[edit]
sdl-android has a strange mechanism for including data files. They are treated as downloads. You can put zip files in project/jni/application/ohrrpgce/AndroidData/ and then instruct the application to unzip them to /sdcard/Android/data/com.hamsterrepublic.ohrrpgce.game/files/ using the following command in AndroidAppSettings.cfg
AppDataDownloadUrl="!Dont Eat Soap Game Data|eatsoap.zip"
In the above example, the eatsoap.zip file contains eatsoap.rpg and ohrrpgce_arguments.txt containing
eatsoap.rpg
This scheme works, but the problem with it is that if you update and re-install the apk, the data file will NOT be re-unzipped.
Apparently the workaround is:
DeleteFilesOnUpgrade="libsdl-DownloadFinished-0.flag"
Which instructs it to delete the file that indicates that the first download in the list has already happened.
This method works, but it means that the first run is slow, and the rpg file effectively exists in two different places on your android device. (and that is not even counting the third copy when the .rpg file is unlumped to a temporary location)
Is a better system even possible[edit]
It would be better to avoid copying the rpg file (or rpgdir) to a different location on the sdcard. I assume when the apk is installed, its files are unpacked to private storage somewhere, but I have not been able to find this location.
There is a java command getFilesDir() to get the private storage location. sdl-android stores the location in an environment variable SECURE_STORAGE_DIR
nativeSetEnv( "SECURE_STORAGE_DIR", p.getFilesDir().getAbsolutePath() );
The value stored in the environment variable will actually be /data/data/com.hamsterrepublic.ohrrpgce.game/files and that folder contains only the libsdl settings file. The parent directory /data/data/com.hamsterrepublic.ohrrpgce.game contains three folders, lib, cache and files
After further reading about "assets" and "resources" I am pretty sure that installing an apk does *not* unpack these files to any location in the filesystem. You have to use java calls to get this data, and if it is even slightly large, it has to be broken into chunks and the app is responsible for reconstructing the whole file from the chunks at runtime.
I was examining the packages generated by the python-for-android project/ and they do appear to be able to unpack data files into /data/data/com.whatever.gamename/files at install time, so the files are ready to use immediately on first run. So I know it is possible, I just don't know how yet.
I was wrong. python-for-android unpacks files on the first run, just like sdl-android. It does however seem to unpack them must faster. I think this is because sdl-android does first-run unpacking from compressed zip files, and python-for-android does it from uncompressed tarballs (which have been renamed to .mp3 file extensions).
So this reaffirms the theory that a better method is impossible non-trivial-- but gives me hope that a *faster* optimization of the existing method is possible.
Using ddms to profile DataDownloader.java I found that almost all of its time seems to be spent in displaying the progress meter, not in actually unzipping or writing. By commenting out one critical .setText() call, I was able to improve the speed of the unzipper by 1/3 to 1/2. However, if I disabled the progress meter entirely, it did not get any faster, even though the profiler said it still spent most of its time in code that seems to be display related.
Yes it is: Assets[edit]
Asset files are stored in project/assets and placed in the .apk. apk files are zip files, and are apparently not decompressed on installation. Each file in a zip file is compressed independently (unlike, say, .tar.gz files). Zip tools like the .apk packager can decide to store data files in the .apk without compressing them; apparently it does this for .mp3, .png and other already-compressed files (based on their file extension? Needs double checking).
Asset files are accessed with int) AssetManager.open. Note the accessMode argument. Randomly seeking within a compressed stream is expensive: to seek forward, you have to decompress all the data up to that point. To seek backwards, you have to either start from the beginning, or store the state of the decompressor at a previous point (e.g. the mark method of InputStream). You can also easily wrap an InputStream with a BufferedReader, which is like using fread() instead of read() in C, and is preferable if performing small reads. So setting the correct accessMode argument when read a compressed asset file may be important. But I have no idea how much ACCESS_RANDOM actually helps.
There is a C interface to AssetManager in the NDK that we can call directly without any Java/sdl-android wrapper. (example)
If we want to read game data directly from the .apk (and without unlumping) we have a lot of things to consider. We should obviously use .rpgdir folders so that each lump can be individually accessed. However whether or each each lump should ideally be compressed varies:
- .ogg music/sfx should be uncompressed. Hopefully the packager does this automatically.
- Graphics files, especially .til and .mxs, are typically a large fraction of the game's size and easily compressed, so they should be to keep size down. But on the other hand since they are single large files randomly seeking in them may be slow!
- Note that we have a graphics cache in front of all files except .mxs, and we can tweak its settings
- Scripts are sometimes large (3.5MB for Powerstick Man), need to be loaded quickly, and are double unlumped. There is also already a cache in front of them. Perhaps continue to double-unlump, but do so on installation/first run instead.
- Map files should be compressed. Since they are read all in one gulp there's no problem.
- Anything else probably doesn't matter.
- We would probably want to read RELOAD files into memory instead of doing deferred loading. And we should use BufferedReader too, since the implementation assumes file IO is buffered.
So in some cases we may want to extract lumps to the cache area, modify existing code to do less random seeking, or modify file formats.
Google Play File Size Limitations[edit]
Unfortunately, even if we solve the problem of loading data directly from assets, there is another data file problem to be solved. The Google Play store imposes a 50mb size limit on all apk files. Music will push many games over this limit, for example, even at 50% completion, Motrya has 80 megabytes of ogg music.
Google Play does allow you to have two expansion files, up to 2 gb each https://developer.android.com/google/play/expansion-files.html
One approach would be to be to use an rpg file as an expansion file. Google play treats expansion files as opaque binary files, and doesn't care what format they are in. We could modify the android port to look for expansion files in the expected location of /sdcard/Android/obb/com.whatever.gamename/main.[VERSIONNUMBERWHENFIRSTINSTALLED].com.whatever.gamename.obb
That is a really stupid naming scheme, but still not hard to work with, by my reading of the docs, there can only ever be one single match to /sdcard/Android/obb/com.whatever.gamename/main.*.com.whatever.gamename.obb ... and in fact, it appears that there can only ever be a single match to /sdcard/Android/obb/com.whatever.gamename/main.*
Unfortunately, it appears that Google Play doesn't guarantee the expansion files actually will be downloaded, and if the download fails, you are expected to re-try the download using a special API from within your app. That sucks.
Release builds[edit]
Apparently, release builds can be created by running
./build.sh release
And the resulting file will be project/bin/MainActivity-release-unsigned.apk
Unfortunately the filename is a lie. It will be signed with the debug key. This is no problem for sideloading and for the OUYA store, but Google Play strictly requires that an apk be signed with only one key, and it can't be the debug key.
The only workaround I have found for this is to manually edit build.sh and comment out the line that calls jarsigner in release mode (I am sure there is a simple way to override the key globally, but for my purposes, each different game needs to be signed with a different keystore.)
After you have a truly unsigned apk, Then you must sign it using jarsigner using a command line something like:
jarsigner \ -sigalg MD5withRSA \ -digestalg SHA1 \ -keystore ~/path/to/your/private/key/filename.keystore \ -storepass "whatever your key password is" \ project/bin/MainActivity-release-unsigned.apk \ nameofyourkey mv project/bin/MainActivity-release-unsigned.apk project/bin/MainActivity-release-unaligned.apk zipalign -v 4 project/bin/MainActivity-release-unaligned.apk project/bin/MainActivity-release-signed.apk
Unfortunately, the ./build.sh script run in release mode will always sign with the debug key. Signing with multiple kyes works, and is allowed by the OUYA sotre, but the Google Play store forbids it. I have modified my own copy of build.sh to disable automatic signing in release mode unless the -s argument is provided.