1

I have a bit of a unique situation. I have a front end QML/Qt UI and a backend application written in C# that I'm developing to replace old software. I was previously using Android tablets with an all in one Xamarin UI. This new combo will replace that application. Problem is on the old Android tablets to put the new software on there we need some way to run our C# backend without having to duplicate it in another language or build it into the UI.

I stripped out everything of the Xamarin UI so now its just a blank UI with a foreground service within the project. I tested out executing/starting the foreground service within the Xamarin activity to make sure it works. The service will continually make a beeping noise every second (as a proof of concept that I can get this to work).

[Service(Exported = true, Name = "com.backend.RUN_BACKEND")]
public class ForegroundBackendService : Service

//This is the function that gets called currently when the intent 
//comes in. I create a notification, start foreground service, 
//launch the process/task, and then return a sticky result.

public override StartCommandResult OnStartCommand(Intent intent, 
StartCommandFlags flags, int startId)

The exported setting should mean that this service will be available to any application on the Android tablet. Another application should be able to use the "com.backend.RUN_BACKEND" name to execute this foreground service.

The Xamarin project/app is named TestProject. And this is running on Android 8.1 minimum / 8.1 target (Qt UI is also running there).

The Qt application is built with Qt 6.5.0 but it seems most of the examples are a bit dated and the libraries like QtAndroid have been replaced.

This code below seems to be close to what I'm looking for but QAndroidApplication::androidActivity().object isn't available anymore it seems. So I'm trying to figure out what the new syntax to execute the service.

QAndroidIntent serviceIntent(QAndroidApplication::androidActivity().object(),                        
"org/qtproject/example/qtandroidservice/QtAndroidService");

QJniObject result = QAndroidApplication::androidActivity().callObjectMethod(
    "startService",
    "(Landroid/content/Intent;)Landroid/content/ComponentName;",
    serviceIntent.handle().object());

What would the syntax/code be to execute my ForegroundBackgroundService named "com.backend.RUN_BACKEND" from the Qt UI?

Thanks ahead of time I really appreciate the help!

###EDIT/UPDATE:###

So I found a link that was sort of helpful.

How do I access Android activity in QT6

That links to this page which I had already come across but I'm now taking a deeper look at it.

QAndroidApplication

So now I am trying to figure out what the equivalent syntax is. I'm new to Qt/Android/Android services so maybe the answer is obvious and I'm just missing it.

Whats confusing me is that QAndroidApplication::androidActivity() has members object() and callObjectMethod(). Here is a list of what is available in QNativeInterface::QAndroidApplication. Its not the same so I'm trying to figure out how to write an equivalent piece of code.

context() : int
hideSplashScreen(int)
isActivityContext() : bool
runOnAndroidMainThread(const std::function<QVariant ()> &, const 
QDeadlineTimer) 
: QFuture<QVariant>
sdkVersion() : int

Based on this information can anyone list out an equivalent bit of code using the new QNativeInterface::QAndroidApplication?

I'm still digging into it so hopefully I can figure it out.

###EDIT 2###

Ok so I had overlooked some comments on that QtBug Report link I posted before. I found some new code.

QJniObject jClass = QJniObject("poc/dialogue/Hello", " 
(Landroid/app/Activity;)V", QtAndroidPrivate::activity()); 
jClass.callMethod<void ("showDialogue");

//This seems to solve part of the problem
QAndroidIntent serviceIntent = 
QAndroidIntent(QtAndroidPrivate::activity(), 
"com.example.MyService"); 

//Now I need some sort of piece of code that is equivalent to this

QJniObject result = 
QAndroidApplication::androidActivity().callObjectMethod(
"startService",
"(Landroid/content/Intent;)Landroid/content/ComponentName;",
serviceIntent.handle().object());

I also need to execute functions from a Java SDK so the first couple lines are still useful.

###EDIT 3###

I have code now that isn't highlighted so I think I'm headed in the right direction.

Does this look right?

QAndroidIntent serviceIntent = 
QAndroidIntent(QtAndroidPrivate::activity(), 
"com.example.MyService");
QJniObject startService = QJniObject();
QJniObject intentResult = 
startService.callObjectMethod("startService",                                                                       
"(Landroid/content/Intent;)Landroid/content/ComponentName;",
serviceIntent.handle().object());

Is there anything obviously wrong with the above code. I'm going to test it now so I guess I'll find out.

###EDIT 4###

I am honing in on what I think is a solution.

//I have this line of code but it produces an error
QAndroidIntent serviceIntent = 
QAndroidIntent(QtAndroidPrivate::activity(), 
"SOB.Bacon.com.backend.RunBackend");

//JNI DETECTED ERROR IN APPLICATION: illegal class name 
//SOB.Bacon/com.backend.RunBackend' F zygote  : runtime.cc:550]    
//(should be of //the form 'package/Class', [Lpackage/Class;' or 
'[[B') F zygote  : runtime.cc:550]     //in call to FindClass

I have tried all interations of "SOB.Bacon/com.backend.RunBackend" such as "SOB/Bacon/com/backend/RunBackend" and "SOB.Bacon.com.backend.RunBackend". Nothing seems to work.

I know for a fact that this service works. I created a second bare bones blank Xamarin project and it does one simple thing the project loads up and using this code it launches the foreground service of the other application. And bam the service starts beeping and I know it worked. It launches it by using ONLY the package name "SOB.Bacon" and the class name "com.backend.RunBackend" thats it. So I should be able to do the exact same thing from Qt.

This is the Xamarin code that launches the exported service in the other Xamarin application. Just to show what I'm trying to achieve in Qt.

const string REMOTE_SERVICE_COMPONENT_NAME = 
"com.backend.RunBackend";
// This is the name of the service, according the value of 
ServiceAttribute.Name
const string REMOTE_SERVICE_PACKAGE_NAME = "SOB.Bacon";

// Provide the package name and the name of the service with a 
ComponentName object.
ComponentName cn = new ComponentName(REMOTE_SERVICE_PACKAGE_NAME, 
REMOTE_SERVICE_COMPONENT_NAME);
Intent serviceToStart = new Intent();
serviceToStart.SetComponent(cn);

StartForegroundService(serviceToStart);

I do not understand why Qt can't seem to find this package/service. It does exist so hopefully I'm just missing something obvious.

Can anyone shed any light on this?

I mean how hard is it to replicate the above Xamarin code in Qt. I feel like I'm so close.

Any help would be very much appreciated I am going in circles now.

###EDIT 5###

I really think I identified the actual issue now so I need help essentially getting my Xamarin service added/linked to the Qt project. I am unsure how to do this.

I followed the tutorial here and I have tried both starting up the function in the service and binding to it and neither work.

QT6 Android Services

I am pretty convinced that the problem is that in the example they created a class QtAndroidService that extends QtService. In my case I just extended Service as QtService isn't available in Xamarin/C#. The examples says this should work.

Here is the thing though. In the example it executes the function for example but calling the method startQtAndroidService located at "org/qtproject/example/qtandroidservice/QtAndroidService".

I looked at my current Qt project and its directory starts off the exact same "org.qtproject.example.myappname". I guess I must have just built the UI off an example and never changed it.

So I think this means that somehow they put the library file QtAndroidService within the Qt project.

I'm assuming its a .so file because when I currently run it I'll get back an error saying "Didn't find class "SOB.Bacon.com.backend.RunBackend.ForegroundBackendService" on path: DexPathList[[],nativeLibraryDirectories=[/system/lib, /system/vendor/lib]]". The class is still named ForegroundBackendService but its not a foreground service anymore I followed the example exactly just kept the same name I'll change it.

If I look in the /system/lib folder on the Android device it is filled with files with lib in the name ending in .so.

If I look in my SOB.Bacon project install directory on the Android device there is a lib folder. I do not see a library with the same name as my service though, the .so files that are in there seem to have nothing to do with anything other than what Xamarin needs.

So first off I do not know how to get Xamarin to generate a .so file. It will make a .dll file but that is for use by other Xamarin applications.

However I was able to create a second standalone Xamarin application that could execute the service that was built into the other Xamarin application.

So I have two routes/questions:

  1. Based on that example how do I link/point Qt to look for the service in a different location on the Android tablet, right now it seems to only be looking in /system/lib or /system/vendor/lib.

  2. If a .so file is required, how was the other xamarin application able to execute the service, and how was ADB able to execute the service? In which case how do I make Qt execute a service that is NOT part of its own package?

Thanks a lot!

Cheesebaron
  • 24,131
  • 15
  • 66
  • 118
HelloWorld
  • 41
  • 4
  • I just came across another link that might be of help to figure this out. [QtBug Report](https://bugreports.qt.io/browse/QTBUG-84382) I am trying to figure out what this means now. – HelloWorld Apr 27 '23 at 23:22
  • Are you going to bind to the foreground service to exchange data? – Cheesebaron Apr 28 '23 at 11:01
  • No I do not need to bind to the service. I just need to start it up. The backend and frontend will talk over a websocket. – HelloWorld Apr 28 '23 at 14:14
  • I updated the original question with a new edit that contains more information I'm half way to a solution it seems. – HelloWorld Apr 28 '23 at 14:42
  • I posted a third and hopefully final update to the question. I'm going to start testing/debugging it and if it works I'll post the solution. – HelloWorld Apr 28 '23 at 16:00
  • I found a new link that is helpful [QtAndroid grant usb permission in android with Qt6](https://stackoverflow.com/questions/75638800/qtandroid-grant-usb-permission-in-android-with-qt6) This post confirms that since all this stuff is new there are not many examples so thats why I'm posting! I am trying to get it to call the SOB.Bacon project at the com.backend.RUN_BACKEND service and it never seems to find the class. I think it might be looking at just the native classes but I'm not sure how to make it look at the other app with the service. – HelloWorld Apr 28 '23 at 23:06
  • Depending on version of Android, you will need to add which Intents you query in you manifest. Also if you can start your intents through ADB, it should also work in your App. Question is whether an external App is allowed to start a ForegroundService, I don't think so. You might need to have to create a BroadcastReceiver which in turn starts it. However, on newer versions on Android there are restrictions there as well. – Cheesebaron Apr 30 '23 at 07:07
  • Thats a great suggestion to use ADB to start the intent I'll do that now. Considering I was able to do it from my second Xamarin project I am pretty sure it should work. I only need to get this to work on Android 8.1/9/10 so hopefully there weren't big changes related to services between those three versions. – HelloWorld May 01 '23 at 14:23
  • So I used the ADB to start the service I ran a startservice command but that gave me an error saying "app is in background uid null" so I changed the command to "adb shell am start-foreground-service SOB.Bacon/com.backend.RunBackend" and that worked the service started up exactly like I expected and started beeping. So I know that the service is available. I think I might need to add something to the Android manifest in the Qt project I'm looking at that now. – HelloWorld May 01 '23 at 14:40
  • And the Qt application will be the primary running app that is visible on the UI so I don't see why it wouldn't be allowed to start a foreground service. Unless I'm missing something? – HelloWorld May 01 '23 at 15:06
  • So I kept getting an error when trying to launch the service saying that it could not find the class on the DexPath where the native libraries are located. That is true it is not there but somehow it works when the second Xamarin project calls the foreground service. So do I need to make Qt look elsewhere to execute it. Also the apk does not have a .so file for my service. Somehow my second Xamarin project was able to execute it so it has to be possible but I'm going in circles now trying to solve this. – HelloWorld May 01 '23 at 16:51
  • Only the App with the Foreground Service can start its own Foreground Service and as you see you are probably not allowed to start it when that App is in the background. Do you need a Foreground Service from the Xamarin App? Perhaps it could be a regular service that you Bind and call into? The way that works is your QT app binds and that exported Service will launch in the QT App's process. – Cheesebaron May 01 '23 at 19:11
  • Well if the foreground service can't be started then how was my second Xamarin project able to start it using just project/package name and classname of the foreground service? Maybe because they are signed the same possibly (they are debug versions not a release). It seems Qt is looking for the package/class in the Dex path which looks in the /lib folder for native interfaces. I think that is why it can't find it I just don't know how to make it look elsewhere. Also I need a foreground service because the backend needs to run 24/7 while the UI is visible as it needs to handle/process traffic – HelloWorld May 02 '23 at 15:22
  • I also setup the service so that it can be bound to but I have no tried to bind to it yet. I bet I'll encounter the same problem though where Qt just can't find the package/class that I want to bind to. I'll look into that now. Regardless I can't just have an intermittently running backend, I want the process to run non stop when the UI is visible and to my knowledge only a foreground service with a notification can do this. – HelloWorld May 02 '23 at 15:25
  • I added one more edit to the end of the original post. I am 99% sure I know what the problem is now just have to figure out how to fix it. – HelloWorld May 02 '23 at 23:04
  • How do I replicate the following intent in Qt? "Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=SOB.Bacon/com.backend.RunBackend }". This is the exact intent that ADB uses to start up the service, so if ADB can do it from the computer then Qt should be able to do it. I am looking at QtAndroidIntent and its not obvious to me how to replicate this. – HelloWorld May 03 '23 at 17:57

0 Answers0