2e8e0b213a
packing gdbserver into apk conflicts with extractNativeLibs="false", as the gradle pugin compresses it (which could be disabled using aaptOptions), but furthermore it doesn't page-align it, breaking installation. So instead let the user manually push the gdbserver tool to device and remove the hardcoded values that were only there to please the ndk-gdb scripts. Using lldb from within Android Studio is more comfortable anyway :-) Change-Id: I31c3af4847a479c56b3fcd6b5bed114e004bf0d2 Reviewed-on: https://gerrit.libreoffice.org/41950 Tested-by: Jenkins <ci@libreoffice.org> Reviewed-by: Christian Lohmaier <lohmaier+LibreOffice@googlemail.com>
319 lines
13 KiB
Text
319 lines
13 KiB
Text
LibreOffice Android
|
|
*******************
|
|
|
|
Bootstrap
|
|
*********
|
|
|
|
Contains common code for all projects on Android to bootstrap LibreOffice. In
|
|
addition it is a home to LibreOfficeKit (LOK - see libreofficekit/README) JNI
|
|
classes.
|
|
|
|
stuff in source directory
|
|
*************************
|
|
|
|
LibreOffice Android application - the code is based on Fennec (Firefox for Android).
|
|
It uses OpenGL ES 2 for rendering of the document tiles which are gathered from
|
|
LibreOffice using LOK. The application contains the LibreOffice core in one shared
|
|
library: liblo-native-code.so, which is bundled together with the application.
|
|
|
|
Architecture and Threading
|
|
**************************
|
|
|
|
The application implements editing support using 4 threads:
|
|
1. The Android UI thread, we can't perform anything here that would take a considerable
|
|
amount of time.
|
|
2. An OpenGL thread which contains the OpenGL context and is responsible for drawing
|
|
all layers (including tiles) to the screen.
|
|
3. A thread (LOKitThread), that performs LibreOfficeKit calls, which may take more time
|
|
to complete. In addition it also receives events from the soffice thread (see below)
|
|
when the callback emits an event. Events are stored in a blocking queue (thread
|
|
processes events in FCFS order, goes to sleep when no more event is available and
|
|
awakens when there are events in queue again).
|
|
4. A native thread created by LibreOfficeKit (we call it the soffice thread), where
|
|
LibreOffice itself runs. It receives calls from LOKitThread, and may emit callback
|
|
events as necessary.
|
|
|
|
LOKitThread
|
|
***********
|
|
|
|
LOKitThread (org.libreoffice.LOKitThread) communicates with LO via JNI (this can
|
|
be done only for one thread) and processes events (defined in org.libreoffice.LOEvent)
|
|
triggered from UI.
|
|
|
|
Application Overview
|
|
********************
|
|
|
|
LibreOfficeMainActivity (org.libreoffice.LibreOfficeMainActivity) is the entry point
|
|
of the application - everything starts up and tears down from here (onCreate, onResume,
|
|
onPause, onStart, onStop, onDestroy).
|
|
|
|
Document view
|
|
-------------
|
|
|
|
From here on one of the most interesting pieces are the classes around document view,
|
|
which includes listening to touch events, recalculating the viewport, tiled handling
|
|
and rendering the layers to the document.
|
|
|
|
Viewport - the viewport is the currently visible part of the document. It is defined
|
|
by view rectangle and zoom.
|
|
|
|
Layers - document view is rendered using many layers. Such layers are: document
|
|
background, scroll handles, and also the document tiles.
|
|
|
|
Document view classes
|
|
---------------------
|
|
|
|
- LayerView (org.mozilla.gecko.gfx.LayerView) is the document view of the application.
|
|
It uses the SurfaceView (android.view.SurfaceView) as the main surface to draw on
|
|
using OpenGL ES 2.
|
|
|
|
- GLController (org.mozilla.gecko.gfx.GLController) - holder of the OpenGL context.
|
|
|
|
- RenderControllerThread (org.mozilla.gecko.gfx.RenderControllerThread) executes the
|
|
rendering requests through LayerRenderer.
|
|
|
|
- LayerRenderer (org.mozilla.gecko.gfx.LayerRenderer) renders all the layers.
|
|
|
|
- GeckoLayerClient (org.mozilla.gecko.gfx.GeckoLayerClient) is the middle man of the
|
|
application, which connects all the bits together. It is the document view layer
|
|
holder so the any management (including tiled rendering) usually go through this
|
|
class. It listens to draw requests and viewport changes from PanZoomController
|
|
(see "Touch events").
|
|
|
|
Touch events, scrolling and zooming
|
|
-----------------------------------
|
|
|
|
The main class that handles the touch event, scrolling and zooming is JavaPanZoomController
|
|
org.mozilla.gecko.gfx.JavaPanZoomController (implementation of PanZoomController interface).
|
|
When the user performs a touch action, the document view needs to change, which means the
|
|
viewport changes. JavaPanZoomController changes the viewport and signals the change through
|
|
PanZoomTarget (org.mozilla.gecko.gfx.PanZoomTarget).
|
|
|
|
TiledRendering
|
|
--------------
|
|
|
|
Tiled rendering is a technique that splits the document to bitmaps of same size (typically
|
|
256x256) which are fetched on demand.
|
|
|
|
In the application the ComposedTileLayer (org.mozilla.gecko.gfx.ComposedTileLayer) is the
|
|
layer responsible for tracking and managing the tiles. Tiles are in this case also layers
|
|
(sub layers?) implemented in SubTile (org.mozilla.gecko.gfx.SubTile), where each one is
|
|
responsible for one tile bitmap (actually OpenGL texture once it has been uploaded).
|
|
|
|
When the viewport changes, the request for tile rechecking is send to LOKitThread (see
|
|
LOKitThread#tileReevaluationRequest), where the tiles are rechecked, add and removed if
|
|
necessary.
|
|
|
|
CompositeTileLayer is actually an abstract class, which has two implementations. One is
|
|
DynamicTileLayer (org.mozilla.gecko.gfx.DynamicTileLayer), which is used for main tile
|
|
view of the document, and FixedZoomTileLayer (org.mozilla.gecko.gfx.FixedZoomTileLayer),
|
|
which just renders the tiles at a fixed zoom level. This is then used as a background
|
|
low resolution layer.
|
|
|
|
Tile invalidation
|
|
-----------------
|
|
|
|
Tile can change in LibreOffice when user changes the content (adds, removes text or changes
|
|
the properties). In this case, an invalidation rectangle is signaled from LibreOffice, which
|
|
includes a rectangle that needs to be invalidated. In this case LOKitThread gets this request
|
|
via callback, and rechecks all tiles if they need to be invalidated. For more details see
|
|
LOKitThread#tileInvalidation).
|
|
|
|
Editing
|
|
*******
|
|
|
|
For editing there are 2 coarse tasks that the LibreOffice app must do:
|
|
1. send input events to LibreOffice core (keyboard, touch and mouse)
|
|
2. listen to messages (provided via callback) from LibreOffice core and react accordingly
|
|
|
|
In most cases when an input event happens and is send to the LO core, then a message from
|
|
LO core follows. For example: when the user writes to the keyboard, key event is sent and
|
|
a invalidation request from LO core follows. When user touches an image, a mouse event is
|
|
sent, and a "new graphic selection" message from LO core follows.
|
|
|
|
All keyboard and touch events are send to LOKitThread as LOEvents. In LOKitThread they are
|
|
processed and send to LibreOffice core. The touch events originate in JavaPanZoomController,
|
|
the keyboard events in LOKitInputConnectionHandler (org.libreoffice.LOKitInputConnectionHandler),
|
|
however there are other parts too - depending on the need.
|
|
|
|
InvalidationHandler (org.libreoffice.InvalidationHandler) is the class that is responsible
|
|
to process messages from LibreOffice core and to track the state.
|
|
|
|
Overlay
|
|
*******
|
|
|
|
Overlay elements like cursor and selections aren't drawn by the LO core, instead the core
|
|
only provides data (cursor position, selection rectangles) and the app needs to draw them.
|
|
DocumentOverlay (org.libreoffice.overlay.DocumentOverlay) and DocumentOverlayView
|
|
(org.libreoffice.overlay.DocumentOverlayView) are the classes that provide the overlay over
|
|
the document, where selections and the cursor is drawn.
|
|
|
|
|
|
Icons
|
|
*****
|
|
|
|
App uses material design icons available at [1].
|
|
|
|
|
|
[1] - https://www.google.com/design/icons/
|
|
|
|
Emulator and debugging notes
|
|
****************************
|
|
|
|
For instructions on how to build for Android, see README.cross.
|
|
|
|
* Getting something running
|
|
|
|
Attach your device, so 'adb devices' shows it. Then run:
|
|
|
|
cd android/source
|
|
make install
|
|
adb logcat
|
|
|
|
and if all goes well, you should have some nice debug output to enjoy when you
|
|
start the app.
|
|
|
|
* Using the emulator
|
|
|
|
Create an AVD in the android UI, don't even try to get the data partition size
|
|
right in the GUI, that is doomed to producing an AVD that doesn't work.
|
|
Instead start it from the console:
|
|
|
|
LD_LIBRARY_PATH=$(pwd)/lib emulator-arm -avd <Name> -partition-size 500
|
|
|
|
where <Name> is the literal name of the AVD that you entered.
|
|
|
|
[ In order to have proper acceleration, you need the 32-bit libGL.so:
|
|
|
|
sudo zypper in Mesa-libGL-devel-32bit
|
|
|
|
and run emulator-arm after the installation. ]
|
|
|
|
Then you can run ant/adb as described above.
|
|
|
|
After a while of this loop you might find that you have lost a lot of
|
|
space on your emulator's or device's /data volume. You can do:
|
|
|
|
adb shell stop; adb shell start
|
|
|
|
Debugging
|
|
---------
|
|
|
|
First of all, you need to configure the build with --enable-debug or
|
|
--enable-dbgutil. You may want to provide --enable-selective-debuginfo too,
|
|
like --enable-selective-debuginfo="sw/" or so, in order to fit into the memory
|
|
during linking.
|
|
|
|
Building with all symbols is also possible but the linking is currently
|
|
slow (around 10 to 15 minutes) and you need lots of memory (around 16GB + some
|
|
swap).
|
|
|
|
* Using ndk-gdb
|
|
|
|
Direct support for using ndk-gdb has been removed from the build system. It is
|
|
recommended that you give the lldb debugger a try that has the benefit of being
|
|
nicely integrated into Android Studio (see below for instructions).
|
|
If you nevertheless want to continue using ndk-gdb, use the following steps
|
|
that are described in more detail here: https://stackoverflow.com/a/10539883
|
|
|
|
- add android:debuggable="true" to AndroidManifest.xml
|
|
- push gdbserver to device, launch and attach to application
|
|
- forward debugging port from host to device
|
|
- launch matching gdb on host and run following setup commands:
|
|
- set solib-search-path obj/local/<appAbi>
|
|
- file obj/local/<appAbi>/liblo-native-code.so
|
|
- target remote :<portused>
|
|
|
|
Pretty printers aren't loaded automatically due to the single shared
|
|
object, but you can still load them manually. E.g. to have a pretty-printer for
|
|
rtl::OString, you need:
|
|
|
|
(gdb) python sys.path.insert(0, "/master/solenv/gdb")
|
|
(gdb) source /master/instdir/program/libuno_sal.so.3-gdb.py
|
|
|
|
* Using Android Studio (and thus lldb)
|
|
|
|
Note that lldb might not yield the same results as ndk-gdb. If you suspect a
|
|
problem with lldb, you can try to manually use ndk-gdb as described above.
|
|
Using lldb from within Android Studio is more comfortable though and works like this:
|
|
|
|
- open android/source/build.gradle in Android Studio via File|New → Import Project
|
|
- make sure you select the right build variant (strippedUIDebug is what you want)
|
|
- use Run|Edit Configurations to create a new configuration of type "Android Native"
|
|
- on tab "General" pick module "source"
|
|
- on tab "Native Debugger" add android/source/obj/local/<hostarch> to
|
|
the Symbol directories
|
|
|
|
Then you can select your new configuration and use Run | Debug to launch it.
|
|
Note that lldb doesn't initially stop execution, so if you want to add
|
|
breakpoints using lldb prompt, you manually have to pause execution, then you
|
|
can switch to the lldb tab and add your breakpoints. However making use of the
|
|
editor just using File|Open .. to open the desired file in Android Studio and
|
|
then toggling the breakpoint by clicking on the margin is more comfortable.
|
|
|
|
* Debugging the Java part
|
|
|
|
Open android/source/build.gradle in Android studio via File|New → Import
|
|
Project and you can use Android Studio's debugging interface.
|
|
Just make sure you pick the correct build variant (strippedUIDebug)
|
|
|
|
The alternative is to use the jdb command-line debugger. Steps to use it:
|
|
|
|
1) Find out the JDWP ID of a debuggable application:
|
|
|
|
adb jdwp
|
|
|
|
From the list of currently active JDWP processes, the last number is the just
|
|
started debuggable application.
|
|
|
|
2) Forward the remote JDWP port/process ID to a local port:
|
|
|
|
adb forward tcp:7777 jdwp:31739
|
|
|
|
3) Connect to the running application:
|
|
|
|
jdb -sourcepath src/java/ -attach localhost:7777
|
|
|
|
Assuming that you're already in the LOAndroid3 directory in your shell.
|
|
|
|
* Debugging the missing services
|
|
|
|
Android library only include essential services that are compiled for
|
|
LibreOffice in order to reduce the size of the apk. When developing,
|
|
some services might become useful and we should add those services
|
|
to the combined library.
|
|
|
|
In order to identify missing services, we need to be able to receive
|
|
SAL_INFO from cppuhelper/source/shlib.cxx in logcat and therefore identify
|
|
what services are missing. To do so, you may want add the following
|
|
when configuring the build.
|
|
|
|
--enable-selective-debuginfo="cppuhelper/ sal/"
|
|
|
|
Which services are combined in the android lib is determined by
|
|
|
|
solenv/bin/native-code.py
|
|
|
|
* Common Errors / Gotchas
|
|
|
|
lo_dlneeds: Could not read ELF header of /data/data/org.libreoffice...libfoo.so
|
|
This (most likely) means that the install quietly failed, and that
|
|
the file is truncated; check it out with adb shell ls -l /data/data/....
|
|
|
|
* Startup details
|
|
|
|
All Android apps are basically Java programs. They run "in" a Dalvik
|
|
(or on Android 5 or newer - ART) virtual machine. Yes, you can also
|
|
have apps where all *your* code is native code, written in a compiled
|
|
language like C or C++. But also such apps are actually started
|
|
by system-provided Java bootstrapping code (NativeActivity) running
|
|
in a Dalvik VM.
|
|
|
|
Such a native app (or actually, "activity") is not built as a
|
|
executable program, but as a shared object. The Java NativeActivity
|
|
bootstrapper loads that shared object with dlopen.
|
|
|
|
Anyway, our current "experimental" apps are not based on NativeActivity.
|
|
They have normal Java code for the activity, and just call out to a single,
|
|
app-specific native library (called liblo-native-code.so) to do all the
|
|
heavy lifting.
|