Dienstag, 23. August 2016

Native Barcode Scanner Integration

1) Introduction

There are lots of solutions, showing how to implement a barcode scanner into your FireMonkey app.

There are different problems with all of these solutions:
  • not platform independent
  • platform dependent API handling
  • using external libraries
  • missing code formats (f.e. no QR-Code support)
  • cost a lot of money
  • confusing or difficult licences
  • expect another app to be installed (BarcodeScanner)
  • [...]
Especially the last problem is quite uncharming, because we can't demand to install another app to use our app.
On Android the Java ZXing library is used, which is not installed by default on the customers device. So the Google Barcode Scanner app need to be installed from PlayStore.

2) Solution

A million thanks to the work of Spelt! He migrated the Java ZXing open-source API to Delphi!
And the best thing about it: It works!


The reason, why I post about external code is, because in my opinion, he didn't got enough credit for his effort.
In these times, where everywhere QR-Codes are used, a barcode scanner is fundamental!

  • It's free!
  • native code for Windows/Android/iOS/OSX
  • Simple and fast
  • Multiple barcodes: QR-Code, Code-128, Code-93, ITF


3) Integration

We want build a mini application with a single form displaying the camera, framed by a darkened area and scanning line.

The form is scanning on the fly when starting the application. 
If a code was recognized, we'd like to stop scanning and showing up the content in a message dialog.

I will not explain how to build a FireMonkey form. Therefor take a look at tons of tutorials all around the internet.
[...]

constructor TMyBarcodeReader.Create();
var lAppEventSvc : IFMXApplicationEventService;
begin
  inherited;

  fCamera := TCameraComponent.Create(nil);
  fCamera.Kind      := FMX.Media.TCameraKind.ckFrontCamera;
  fCamera.FlashMode := FMX.Media.TFlashMode.fmFlashOff;
  fCamera.Quality   := FMX.Media.TVideoCaptureQuality.MediumQuality;
  fCamera.OnSampleBufferReady := doOnCameraSampleBufferReady;

  fFrameTake := 0;

  if TPlatformServices.Current.SupportsPlatformService(IFMXApplicationEventService, IInterface(lAppEventSvc)) then
    lAppEventSvc.SetApplicationEventHandler(AppEvent);

  fScanManager := TScanManager.Create(TBarcodeFormat.Auto, nil);
end;

procedure TMyBarcodeReader.start();
begin
  fResultReceived := false;

  fCamera.Active := false;
  fCamera.Kind   := FMX.Media.TCameraKind.BackCamera;
  fCamera.Active := true;
end;

procedure TMyBarcodeReader.stop();
begin
  fCamera.Active := false;
end;

procedure TMyBarcodeReader.doOnCameraSampleBufferReady(Sender: TObject; const ATime: TMediaTime);
begin
  TThread.Synchronize(TThread.CurrentThread, getImage);
end;

procedure TMyBarcodeReader.getImage();
var lScanBitmap : TBitmap;
    lReadResult : TReadResult;
    lImgObj     : TObject;
    lOutBitmap  : TBitmap;
begin
  // get the TImage where we want to display the camera image
  if assigned(fOnDisplayImage) then fOnDisplayImage(lImgObj)
                               else lImgObj := nil;

  if (not assigned(lImgObj)) or not (lImgObj is TBitmap) then exit;
  lOutBitmap := TBitmap(lImgObj);

  fCamera.SampleBufferToBitmap(lOutBitmap, true);

  if fScanInProgress then
    exit;

  lScanBitmap := TBitmap.Create();
  lOutBitmap.Assign(lScanBitmap);
  TTask.Run(
    procedure
    begin
      try
        if not fResultReceived then
        begin
          fScanInProgress := true;
          lReadResult     := fScanManager.Scan(lScanBitmap);
          fScanInProgress := false;
        end;
      except
        on E: Exception do
        begin
          fScanInProgress := false;
          TThread.Synchronize(nil,
            procedure
            begin
              //
            end);

          if assigned(lScanBitmap) then
            freeAndNil(lScanBitmap);

          exit;
        end;

      end;

      TThread.Synchronize(nil,
        procedure
        begin
          if assigned(lReadResult) then
          begin
            fResultReceived := true;
            if assigned(fOnResult) then
              fOnResult(lReadResult.Text);

            stop();
          end;

          if assigned(lScanBitmap) then
            freeAndNil(lScanBitmap);

          freeAndNil(lReadResult);
        end);
    end);

end;

// Make sure the camera is released if you're going away
function TMyBarcodeReader.appEvent(pAppEvent : TApplicationEvent; pContext : TObject) : Boolean;
begin
  case pAppEvent of
    TApplicationEvent.WillBecomeInactive :
      fCamera.Active := false;
    TApplicationEvent.EnteredBackground :
      fCamera.Active := false;
    TApplicationEvent.WillTerminate :
      fCamera.Active := false;
  end;
end;

[...]
This is just an excerpt on how to implement the API. Spelt also got a demo, where you can have a look at.

Hint:

Simply build a Form with an TImage in the back. Add 4 black rectangles with transparency as a darkened frame and a red line, symbolizing the ancient scanning line.
Then include the TMyBarcodeReader component.

Inside TMyBarcodeReader.Create() we create the logical TCameraComponent and the ScanManager. Here the TMyBarcodeReader.doOnCameraSampleBufferReady() event is applied to the TCameraComponent. 
So, every time we get a new camera image, we enter this event.


constructor TMyBarcodeReader.Create();
var lAppEventSvc : IFMXApplicationEventService;
begin
  inherited;

  fCamera := TCameraComponent.Create(nil);
  fCamera.Kind      := FMX.Media.TCameraKind.ckFrontCamera;
  fCamera.FlashMode := FMX.Media.TFlashMode.fmFlashOff;
  fCamera.Quality   := FMX.Media.TVideoCaptureQuality.MediumQuality;
  fCamera.OnSampleBufferReady := doOnCameraSampleBufferReady;

  fFrameTake := 0;

  if TPlatformServices.Current.SupportsPlatformService(IFMXApplicationEventService, IInterface(lAppEventSvc)) then
    lAppEventSvc.SetApplicationEventHandler(AppEvent);

  fScanManager := TScanManager.Create(TBarcodeFormat.Auto, nil);
end;

With TMyBarcodeReader.start() we startup the scanning process, by activating the back camera.


procedure TMyBarcodeReader.start();
begin
  fResultReceived := false;

  fCamera.Active := false;
  fCamera.Kind   := FMX.Media.TCameraKind.BackCamera;
  fCamera.Active := true;
end;

When the camera service provides the current camera image, we automatically enter the TMyBarcodeReader.doOnCameraSampleBufferReady() event, where we synchronize our "getImage" method.

TMyBarcodeReader.getImage() is the main handling routine. At first we want to get the destination image by the "OnDisplayImage" event, to know where to display the
camera image.
  // get the TImage where we want to display the camera image
  if assigned(fOnDisplayImage) then fOnDisplayImage(lImgObj)
                               else lImgObj := nil;

  if (not assigned(lImgObj)) or not (lImgObj is TBitmap) then exit;
  lOutBitmap := TBitmap(lImgObj);
Then we're requesting the TCameraComponent image itself directly onto our form image.
fCamera.SampleBufferToBitmap(lOutBitmap, true);
In the next step we're about to run a TTask where we want to scan the latest camera buffered image.
if not fResultReceived then
begin
  fScanInProgress := true;
  lReadResult     := fScanManager.Scan(lScanBitmap);
  fScanInProgress := false;
end;
If we found a code, we'll stop the scanning process by deactivating camera and entering the "OnResult" event.
if assigned(lReadResult) then
begin
  fResultReceived := true;
  if assigned(fOnResult) then
    fOnResult(lReadResult.Text);

  stop();
end;
The last method you see, is the platform service event. When the app status changes, for example, if it's becoming inactive, we do not need to scan anymore. So we're deactivating the camera component.

function TMyBarcodeReader.appEvent(pAppEvent : TApplicationEvent; pContext : TObject) : Boolean;
begin
  case pAppEvent of
    TApplicationEvent.WillBecomeInactive :
      fCamera.Active := false;
    TApplicationEvent.EnteredBackground :
      fCamera.Active := false;
    TApplicationEvent.WillTerminate :
      fCamera.Active := false;
  end;
end;

4) Conclusion

As always I'd like to thank you for reading my blog. Let me know if there are some bugs, mistakes or problems with this article or component.



(*) These links are being provided as a convenience and for informational purposes only; they do not constitute an endorsement or an approval by this blog of any of the products, services or opinions of the corporation or organization or individual. This blog bears no responsibility for the accuracy, legality or content of the external site or for that of subsequent links. Contact the external site for answers to questions regarding its content.

Samstag, 30. April 2016

OpenWeatherMap integration

1) Introduction

Some of you may always wanted to show up the current weather at a specific location or connect weather information with your app data.

For example: In case you've got a skiing app, you only want to recommend slopes where the wether and temperature is suitable.



2) The Effort

We want to build an app where we can request weather information by city, longitude/latitude, zipcode and country. We also want to show up a 5 day forecast for a specific city. And as a small bonus, we want to list a number of weather stations nearby a location.
With the first release of our app we want it for free, because we can't estimate the number of users.
The guys from OpenWeatherMap provide some of their data for free under certain conditions.
But for our demo app it supports all features we'll need.

http://openweathermap.org/(*)


The free version got the following restrictions:
  • less equal 60 requests per minute
  • current weather information
  • 5 days / every 3 hour forecast
  • data updates in less then 2 hours
  • API version support: only current version
  • NO 16 days / daily forecast
  • NO SSL
  • NO UV-Index data
  • NO bulk download
When our app works and more and more users join, we could switch to a payment model, to handle an increasing access rate.

3) The Integration

3.1.) Get your account

At first we need to sign up for an account, because we'll need an individual API-Key.
At https://home.openweathermap.org/users/sign_up (*) we can easily register.

Afterwards you'll find the API-Key in your profile information.

3.2.) Download OpenWeatherMapClient component

In the next step you're welcome to download my OpenWeatherMapClient component.
This component is platform independent (Windows 32/64, Android, iOS, MacOS).
It was built with Delphi 10.1 Berlin (should also run with older versions, where anonymous procedures and generics are supported). 
The component works with Firemonkey (FMX) and VCL applications. It uses a REST client for gathering information.
The component was designed to use the free version of OpenWeatherMap API, so only allowed methods are implemented.

DownloadOpenWeatherMap.zip

3.3) Usage of OpenWeatherMap component

Inside the zip-file you can find the API client component files and our firemonkey demo app.
  • OpenWeatherMap.pas: TOpenWeatherMap component
  • OpenWeatherMapClient.pas: Encapsulated TRESTClient with easy API access methods
  • OpenWeatherMapInterfaces.pas: interfaces representing the API structure
  • OpenWeatherMapTypes.pas: Interface-Implementations
  • example/fmx/OpenWeatherMapApp.dproj: demo app
  • example/images/...: external weather images for displaying current weather (seperate licence)

Example: 
procedure TForm1.FormCreate(Sender: TObject);
var lWeather : ICurrentWeather;
begin
  fOpenWeatherMap := TOpenWeatherMap.Create(Self);
  fOpenWeatherMap.client.api_key := '<YOUR-API-KEY-HERE>';
  lWeather := fOpenWeatherMap.client.getCurrentWeather('London', '', '', 'metric', 'en');
end;

The component uses interfaces, so if you're not firm with those, please take a closer look at: http://docwiki.embarcadero.com/RADStudio/Seattle/de/Objekt-Interfaces (*)

The API client was encapsulated inside a TOpenWeatherMap-Component, because we'd like to use the IOpenWeatherMapClient inside an external module.

Every IWeatherObject works as a JSON Data wrapper/helper instance. So we're accessing the JSON data directly. This is quite performant when receiving the complex JSON structure. We do not interprete, instead we're only unmarshaling at first.
The data is interpreted on demand, when using a specific property (also sub-interfaces!).

A disadvantage is: when accessing the same simple property (integer, float, string or boolean) multiple times, the JSON data is requested every time.

But in most cases you're reading/writing property values only once.



4) Conclusion

As always I'd like to thank you for reading my blog. Let me know if there are some bugs, mistakes or problems with this article or component.



(*) These links are being provided as a convenience and for informational purposes only; they do not constitute an endorsement or an approval by this blog of any of the products, services or opinions of the corporation or organization or individual. This blog bears no responsibility for the accuracy, legality or content of the external site or for that of subsequent links. Contact the external site for answers to questions regarding its content.

Donnerstag, 7. April 2016

Delphi DX Seattle: TRESTClient SSL connection

1) Introduction

Since Delphi XE8 the REST client component has changed fundamentally. The team of Embarcadero exchanged the Indy component TIdHttpClient with the platform specific native components.
In most cases we don't even recognize something has changed.
But when I recompiled my server and client application the communication was no longer given on mobile devices.


2) The Effort

The core problem I had to deal with, was the usage of an SSL connection as recommend in REST-Protocol.
Until now I used a self signed certificate created with OpenSSL.
Because Indy can ignore certificate problems easily, there was just a warning that my certificate was not trusted (f.e. in Chrome browser).

But since Delphi uses the native HTTP client components things had changed.

REMARK: I will mention here, that it is absolutely the right decision to use native clients, not because of license restrictions and missing gzip functionality, the more because of the proximity to the operating system. A simple question: Why using an external component, when platform specific components already handle everything we need and we do not have to maintain those?

Nevertheless, for SSL communication we need certificates. On Windows OS everything works still the same. But when running my client app on Android 6.0.1 (Nexus 5) the native client shows up its security features.
Because I was using a self signed certificate, Android do not trust it and blocks the communication.

Of course there are ways to ignore this restriction but they seem to me a bit dirty, f.e. instantiate an own TrustManager, which recognizes the certificate and allows access.

A solution is described at the official android documentation:
http://developer.android.com/training/articles/security-ssl.html (*)


3) The solution

At this point, I decided to say goodbye to self signed certificates!
When developing REST applications we want support a safe and user-friendly communication. The user shouldn't decide if he can trust our certificate or not.

Since lately some organizations came up by generating trusted certificates for FREE!
And now everyone can create one at those non-profit certificate authorities (CA). Here's a short list of some of those organizations.
(I take no responsibility for the content)
Nearly every commercial certificate authority also offers free certificates with a limited period. I myself tested the Comodo trial certificate at:
The essence was: It worked! (okey, for 90 days) In the following I will describe the way to get there.

4) Instruction

For generating a certificate at first we'll need a so called CSR, Certificate Signing Request. This personal and server-specific file is going to be delivered to the certificate authority.

4.1) Download the OpenSSL binaries at: https://indy.fulgan.com/SSL/ (*) and extract them on the server, where you're running your server-application

An official certificate got dependencies, which bind it to a specific server-host including the subdomain, f.e. myserver.com is not the same like rest.myserver.com
Also it contains the company and/or the admin information (name, locale, ...).

CAUTION: WE NEED AT LEAST OpenSSL 1.0.2!!! 
I'll explain later, why...


4.2) Generating the CSR

To generate a public Certificate Signing Request (CSR) and a private key, use the folling command:

openssl req -nodes -newkey rsa:2048 -keyout myserver.key -out server.csr

Now you will be asked for your credentials:
Country Name (2 letter code) [AU]: DE
State or Province Name (full name) [Some-State]: MyState
Locality Name (eg, city) []: MyCity
Organization Name (eg, company) [Internet Widgits Pty Ltd]: MyCompany Ltd
Organizational Unit Name (eg, section) []: IT
Common Name (eg, YOUR name) []: mysubdomain.mydomain.com
Email Address []:
A challenge password []: 
An optional company name []:

The e-mail address, a challenge password and an optional company name can be left blank.

If everything worked well, you'll now find two files: Key-File myserver.key and CSR-File server.csr

It's important to keep and backup both files!

The Key-File we'll need later in our Indy-Http-Server again.


4.3) Certificate authority registration

Now it's time to choose your CA and to let them generate the certificates for you, by submitting the created CSR-File.


4.4) Received files

In my case, I received four certificate files by Comodo.
  • your_domain_com.crt
  • AddTrustExternalCARoot.crt
  • COMODORSAAddTrustCA.crt
  • COMODORSADomainValidationSecureServerCA.crt
Normally we need to convert each to the PEM format, which the TIdHttpServer understands, by:
openssl x509 -in your_domain_com.crt -out your_domain_com.pem -outform PEM
BUT this is not the correct solution! We have to combine all of them into one single file, because there are dependencies between those certificates. And if we leave out even one, the complete authentication process will fail.


4.5) Bundle certificates

There is a reason why we're getting all these certificates by COMODO. That are so called intermediate certificates.
And of course we have to implement all of them in our Indy server.
"But what the hell?! I've only got 3 properties in my SSLOptions?!"

This is why we have to create a certificate bundle. To do that, use the following command:

type your_domain_com.crt COMODORSADomainValidationSecureServerCA.crt COMODORSAAddTrustCA.crt AddTrustExternalCARoot.crt > your_domain_com_bundle.pem
This simply concatenates a number of files into a new file. The equivalent on Linux to the "type" command is "cat".

Here we'll get to the point, why we need to use at least OpenSSL 1.0.2. Because only since that version the so called chained files are supported.
Otherwise we'll get an exception by Indy on loading our bundled certificates:
error:00000000:lib(0):func(0):reason(0)
We concatenate our certificates into your_domain_com_bundle.pem file. The extension PEM is important. Indy servers only understand this file format.


4.6) Linking with Indy-Server

Afterwards we'll implement three files in our TIdHttpServer:
  • Root-Certificate = AddTrustExternalCARoot.pem
  • Key-File = myserver.key (which we've generated parallel to the CSR-file)
  • Base-Certificate = your_domain_com_bundle.pem (bundled certificates)
lServerIOHandler := TIdServerIOHandlerSSLOpenSSL.Create(fHttpServer);
lServerIOHandler.SSLOptions.CertFile     := 'your_domain_com_bundle.pem';
lServerIOHandler.SSLOptions.KeyFile      := 'myserver.key';
lServerIOHandler.SSLOptions.RootCertFile := 'AddTrustExternalCARoot.pem';
lServerIOHandler.SSLOptions.Mode         := TIdSSLMode.sslmServer;
lServerIOHandler.SSLOptions.SSLVersions  := [sslvSSLv2, sslvSSLv3, sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2];
lServerIOHandler.SSLOptions.VerifyDepth  := 0; // 0 == ALL CERTIFICATES!
lServerIOHandler.SSLOptions.VerifyMode   := [];
fHttpServer.IOHandler := lServerIOHandler;


5.) Conclusion

A few years ago, this way shouldn't be an option for a lot of people. Not everyone is a member of a company or has the money to afford an expensive certificate.

But because of security improvements, f.e. by mobile platforms and because of non-profit certificate authorities, there is no more reason to generate and use self-signed certificates.

If you find any mistakes in this description or got an addition, feel free to leave a comment.


(*) These links are being provided as a convenience and for informational purposes only; they do not constitute an endorsement or an approval by this blog of any of the products, services or opinions of the corporation or organization or individual. This blog bears no responsibility for the accuracy, legality or content of the external site or for that of subsequent links. Contact the external site for answers to questions regarding its content.

Montag, 21. März 2016

Delphi DX Seattle: Augmented Reality

1.) Introduction


Since Delphi XE5 I was trying to use sensors. But it never worked. With Delphi DX I found new hope in a lot of bugfixes since then. So I've tried to build an ambigious project with augmented reality.

The first step was to compile the official gyroscope demo and run it on Android 6.0.1.
And the nightmare began...

I will not explain how aweful it was to gather some insider informationen and to find out one to another bug.

The more I will describe a way to fix some of those problems.

This explanation reflects my individual experiance taken from several tests and code analysis. So if I'm completely wrong, let me know.

For my tests I used the LG Nexus 5X with Android 6.0.1.


2.) The Effort


To build an augmented reality app, where you can rotate a 3D camera inside of an 3D environment with the device-camera stream in the background, we'll need sensors to detect device movement at first.
In detail: we need some kind of gyroscope. That's why I started with the gyroscope demo by Embarcadero.

Take a look at this official Embarcadero Youtube video:
https://youtu.be/O-0DmmtwpEw?t=2019 (*)


Android itself offers some sensors (hardware and logical sensors) for solving this task:

  • usage of raw Gyroscope sensor data
  • fusion of 3 sensors: Accelerometer, Magnetometer and Gyroscope
  • Orientation Sensor
  • Rotation Vector, Game Rotation Vector and Geomagnetical Rotation Vector Sensor


3.) Analyzation


After reading lots of blogs and documentations this list reduces. Take a look at the official Android documentation: 

http://developer.android.com/guide/topics/sensors/sensors_overview.html (*)


  1. Using sensor raw data is definitly a bad idea, because it doesn't matter, which hardware sensor you'll choose, each and every one is NOT PERFECT.
  2. So the way is to fuse different sensors. Up to Android API 18 we have to do this completely on our own!
  3. Until Android 2.2 a simple Orientation Sensor existed. But it's deprecated and so I will no longer refer to it here.
  4. Since API 18 Android offers the Rotation Vector, Geomagnetical Rotation Vector and the Game Rotation Vector Sensor. The difference is the hardware components used for fusion. All three types are logical, which means they use specific hardware sensors and combine them by complex software algorithms. The mainly recommended sensor is the Rotation Vector Sensor.




4.) Implementation


So far, so good ...

By this recommendation I chose to use the Rotation Vector Sensor.

In my expectations this would deliver already smoothed rotation values.
I thought so ...

In Delphi it can be accessed by the TOrientationSensor component and the Inclinometer3D type.
When implementing a TOrientationSensor in Delphi we can access the TiltX, TiltY and TiltZ and this should return the device rotation information.
But if we start the demo on Android 6.0 the result is not satisfying at all. The cube is completly going crazy, it's flickering and unstable.



4.1.) Problem #1: Bug inside the Delphi sensor framework

In my opinion the implementation of Android sensors is not correct!

Besides that, the Game Rotation Vector and Geomagnatical Rotation Vector Sensors are not implemented at all.

Back to the Rotation Vector Sensor implemenation: When accessing f.e. the TiltX property a getter function will request multiple ASensorEvents to get the specific event and finally retrieve the TiltX value.

function TNativeSensor.LastValue: ASensorEvent;
var  SensorEvent: ASensorEvent;
begin 
  while ASensorEventQueue_getEvents(FNativeEventQueue, @SensorEvent,1) > 0 do 
  begin   
    FLastSensorEvent := SensorEvent; 
  end;  
  Result := FLastSensorEvent;
end;

Because it is done by every property access on TiltX, TiltY, TiltZ, the x,y,z values may come from different sensor events, which leads to inconsistent data.

Between the property access the device is still in movement, so pitch,roll and yaw are still changing. So the correct way should be to gather those coordinates at once, from one single event.

Another inaccuracy the Rotation Vector Sensor is not delivering a vector (Array) of 3 float values. Instead it returns a quaternion with 5 float values: x,y,z,w + c

For simple rotation the x,y,z should be enough. Maybe Embarcadero thought so...
But when combining those values with other sensor values, like from gyroscope, we need more information.

Alright, we could recalculate the missing parts, but why should we waste performance, for what we've already got.



4.2.) Problem #2: Incompatible SDK & NDK version

Delphi DX Seattle compiles Android apps with an older SDK and NDK version: SDK for Android 5.1 and NDK 9c.
The most apps are compilable and runnable on Android 6+. But for sensors those versions are not compatible.
For Android 6.0 we'll need at least SDK 24.4.1 and NDK 10e. (I've also tried NDK 11a & 11b, but those are not runable on my device.)
When creating a SDK version in your Delphi IDE, take care of linking the NDK paths to the highest API version (not android-14!)

Tools > Options > SDK-Manager

SDK 24.4.1:

  • C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-sdk-windows-current
  • C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-sdk-windows-current\build-tools\23.0.2\zipalign.exe
  • C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-sdk-windows-current\tools\Android.bat
  • C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-sdk-windows-current\platform-tools\Adb.exe
  • C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-sdk-windows-current\build-tools\23.0.2\aapt.exe
  • C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-sdk-windows-current\platforms\android-23

"android-sdk-windows-current" is a secondary directory, where I check out the current official Android version.

To get the 23.0.2 build tools select the specific package in the SDK Manager of Android and download it.


NDK 10e:


  • C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-ndk-r10e
  • C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-ndk-r10e\toolchains\arm-linux-androideabi-4.9\prebuilt\windows\bin\arm-linux-androideabi-ld.exe
  • C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-ndk-r10e\toolchains\arm-linux-androideabi-4.9\prebuilt\windows\bin\arm-linux-androideabi-strip.exe
  • C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-ndk-r10e\prebuilt\android-arm\gdbserver\gdbserver
  • C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-ndk-r10e\platforms\android-21
  • C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-ndk-r10e\platforms\android-21\arch-arm\usr\lib
  • C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-ndk-r10e\sources\cxx-stl\gnu-libstdc++\4.9\libs\armeabi-v7a;C:\Users\Public\Documents\Embarcadero\Studio\17.0\PlatformSDKs\android-ndk-r10e\platforms\android-21\arch-arm\usr\lib

Take care of the red marked parts. When creating a new SDK Version in Delphi this is not linked correctly by the IDE.


Java 1.8.0.74:

I compiled with the latest Java version, but it should also be runnable with an older one.


  • C:\Program Files (x86)\Java\jdk1.8.0_74\bin\KeyTool.exe
  • C:\Program Files (x86)\Java\jdk1.8.0_74\bin\JarSigner.exe




4.3.) Problem #3: Android Update Interval

Sensors have a update interval, by what values are requested from hardware.
Here Android 6+ supports only values LARGER THAN 18019 microseconds.
So we need to set the UpdateInterval property for every sensor to at least 18020 microseconds.

Inside of Delphi we've got another inaccuracy. This property should be a long integer type instead of a double, but that may just be meticulous by me. Very often I see people giving in a milliseconds value, as delphi typical, but it needs to be microseconds!



4.4.) Problem #4: Rotation Vector Sensor still got gyro drift

When eliminating problem #1, by redesigning the Android sensor implementation, we'll get much more stable rotation values.
Nevertheless, against any expectations the delivered Rotation Vector Sensor data still got some gyro drift, which leads to unsatisfying results.
Try to shake your device a little bit and you will see, what I mean.

Here we get back to section 3.2.): We need to fuse those values with another hardware sensor.

Alexander Pacha also recognized this inaccuracy and built an open source Java Android app:
https://bitbucket.org/apacha/sensor-fusion-demo (*)

He defined two new external logical sensors:

  • Improved Orientation Sensor 1 (Sensor fusion of Android Rotation Vector and Calibrated Gyroscope - less stable but more accurate)
  • Improved Orientation Sensor 2 (Sensor fusion of Android Rotation Vector and Calibrated Gyroscope - more stable but less accurate)



5.) Conclusion

I hope this helped some of you, struggling with the same difficulties. Maybe Embarcadero is about to change it's library, so we'll need no workaround or new sensor library. 


(*) These links are being provided as a convenience and for informational purposes only; they do not constitute an endorsement or an approval by this blog of any of the products, services or opinions of the corporation or organization or individual. This blog bears no responsibility for the accuracy, legality or content of the external site or for that of subsequent links. Contact the external site for answers to questions regarding its content.