How to send (from Native code) a android.content.Context parameter to a Java fct (via JNI)

1.5k views Asked by At

I am developping an application for my android phone, and I am trying to enable the Wifi hotspot. I am using Qt 5.4.1 so I developp in C++. As there is not any function to do this in the NDK, I am using JNI to call Java Methods.

My java code is (thanks to Ashish Sahu's answer in stackoverflow thread) :

package org.app.test;

import android.content.*;
import android.net.wifi.*;
import java.lang.reflect.*;

//Class that handles Wifi Hotspot (access point) configuration
public class ApManager {

    //Is Wifi hotspot on or off ?
    public static boolean isApOn(Context context) {
        WifiManager wifimanager = (WifiManager) context.getSystemService(context.WIFI_SERVICE);
        try {
            Method method = wifimanager.getClass().getDeclaredMethod("isWifiApEnabled");
            method.setAccessible(true);
            return (Boolean) method.invoke(wifimanager);
        }
        catch (Throwable ignored) {}
        return false;
    }

    //Turn Wifi hotspot on or off
    public static boolean configApState(Context context, boolean b) {
        WifiManager wifimanager = (WifiManager) context.getSystemService(context.WIFI_SERVICE);
        WifiConfiguration wificonfiguration = null;
        try {
            //if Wifi is on, turn it off
            if(isApOn(context)) {
                wifimanager.setWifiEnabled(false);
            }
            Method method = wifimanager.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class);
            method.invoke(wifimanager, wificonfiguration, b);
            return true;
        }
        catch(Exception e) {
            e.printStackTrace();
        }
        return false;
    }       
}

C++ code sample :

setWifiApEnabled(QAndroidJniObject context, bool b)
{
    return QAndroidJniObject::callStaticMethod<jboolean>("org/app/test/ApManager"
                                                         , "configApState"
                                                         , "(Ljava/lang/Object;Z)Z" //Or (Landroid/content/Context;Z)Z ???
                                                         , context.object<jobject>()
                                                         , b);
}

But now I have a problem ; how to get the parameter context to pass to the function setWifiApEnabled(context, b) when I call it ?

I am a little lost, I read some threads about this problem (like this one) but I do not totally understand what the people who answered meant.

Could you help me with this ?

EDIT : I found on this stackoverflow thread a way to get the context :

interface = QApplication::platformNativeInterface();
activiti = (jobject)interface->nativeResourceForIntegration("QtActivity");
at = new QAndroidJniObject(activiti);
appctx = at->callObjectMethod("getApplicationContext", "()Landroid/content/Context;");
if(appctx.isValid()) qDebug() << "I am valid !";
else qDebug() << "I ain't valid !";

appctx is valid, but the Wifi hotspot doesn't get enabled and I cannot get its state.

EDIT 2 : : I successfully managed to enable the Wifi hotspot in java, using Android Studio. The code is the following :

WifiApManager.java :

public class WifiApManager {
    private WifiManager wifiMan;
    protected Method setWifiApEnabledMethod, isWifiApEnabledMethod;
    protected final static int MAX_ITER = 10;

    public WifiApManager(WifiManager wifiMan) {
        this.wifiMan = wifiMan;
        getHiddenMethods();
    }

    private void getHiddenMethods() {
        try {
            setWifiApEnabledMethod = wifiMan.getClass().getMethod("setWifiApEnabled", WifiConfiguration.class, boolean.class);
            isWifiApEnabledMethod = wifiMan.getClass().getMethod("isWifiApEnabled");
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    public boolean isWifiApEnabled() {
        try {
            return (Boolean)isWifiApEnabledMethod.invoke(wifiMan);
        } catch (Exception e) {
            return false;
        }
    }

    public boolean isWifiEnabled() {
        return wifiMan.isWifiEnabled();
    }

    public boolean setWifiApEnabled(WifiConfiguration conf, boolean enabled) {
        try {
            return (Boolean) setWifiApEnabledMethod.invoke(wifiMan, conf, true);
        } catch (Exception e) {
            return false;
        }
    }

    public boolean toggleWifi(String ssid) {
        // WifiConfiguration creation:
        WifiConfiguration conf = new WifiConfiguration();
        conf.SSID = ssid;
        conf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);

        // If AP Wifi is enabled, disables it and returns:
        if(isWifiApEnabled()) {
            //setWifiApEnabled(null, false); Won't work, see two further lines
            wifiMan.setWifiEnabled(true);
            wifiMan.setWifiEnabled(false);
            int maxIter = MAX_ITER;
            while (isWifiApEnabled() && maxIter-- >= 0) {
                try {Thread.sleep(500);} catch (Exception e) {}
            }
            return isWifiApEnabled();
        }

        // If standard Wifi is enabled, disables it:
        if (isWifiEnabled()) {
            if (wifiMan.setWifiEnabled(false)) {
                int maxIter = MAX_ITER;
                while (wifiMan.isWifiEnabled() && maxIter-- >= 0) {
                    try {Thread.sleep(500);} catch (Exception e) {}
                }
            }
            if (isWifiEnabled()) {
                return false;
            }
        }

        // Enables AP Wifi
        try {
            if (! setWifiApEnabled(conf, true)) {
                System.out.println("setWifiApEnabledMethod failed.");
                return false;
            }
            int maxIter = MAX_ITER;
            while (! isWifiApEnabled() && maxIter-- > 0) {
                try {Thread.sleep(500);} catch (Exception e) {}
            }
        } catch(Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
}

Main class :

public class AndroidMenusActivity extends Activity implements OnClickListener {
    private WifiApManager wifiMan;
    private ToggleButton wifiButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        wifiMan = new WifiApManager((WifiManager) this.getSystemService(Context.WIFI_SERVICE));
        setContentView(R.layout.activity_android_menus);
        makeUI();
    }

    private void makeUI() {
        LinearLayout subLayout = (LinearLayout) findViewById(R.id.subLayout);
        wifiButton = new ToggleButton(this);
        wifiButton.setTextOn("Disable Wifi");
        wifiButton.setTextOff("Enable AP Wifi");
        wifiButton.setChecked(wifiMan.isWifiApEnabled());
        wifiButton.setOnClickListener(this);
        subLayout.addView(wifiButton);
    }

    @Override
    public void onClick(View sender) {
        if (!wifiButton.equals(sender))
            return;

        AsyncTask<Object, Void, Boolean> task = new AsyncTask<Object, Void, Boolean>() {
            private ToggleButton bt;
            private WifiApManager wm;

            @Override
            protected Boolean doInBackground(Object... args) {
                bt = (ToggleButton) args[0];
                wm = (WifiApManager) args[1];
                return wm.toggleWifi("test.com");
            }
            @Override
            protected void onPostExecute (Boolean result) {
                bt.setChecked(result.booleanValue());
                bt.setEnabled(true);
            }
        };
        wifiButton.setEnabled(false);
        task.execute(wifiButton, wifiMan);
    }
}

But I cannot find a way to do the same in C++, any help ?

1

There are 1 answers

0
DevMultiTech On

Just in order to transfer context object from C++ to JNI, use auto-injection like that:

QtAndroid::androidActivity().callStaticMethod<void>(
      "com/company/project/MyApp",
      "myFunc", // method name
      "(Landroid/content/Context;)V" // auto-injection
    );