Launching a URL link from Cocos2d-x on android?

This is probably off topic, but I have found this forum to be very helpful in answering all manor of questions. I am just finishing up the hybridization of my app for iOS and android. The only piece left is the ability for a user to launch a URL to a web site from the app. On iOS when a user selects the appropriate cocos2d menu item, I simply call:

[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithUTF8String:url]]];

Which works nicely within a #ifdef APPLE statement. What I am looking for is the NDK C*+ equivalent on android. From what I can tell from my searching online it requires that I setup a callback mechanism from within java. Which I have less than 0 knowledge of how to do. I know c/c**/obj-c, but nothing about java. Is there a pure c*+ way to do this from within the NDK?

Thanks!

You had to make a JNI call in this situation.
Please refer to this thread http://www.cocos2d-x.org/boards/10/topics/2595

Ok, I have modified “Cocos2dJni.cpp” to include this:

void openURLJNI(const char* url)
{
TMethodJNI t;
if (getMethodID(t
, “org/cocos2dx/lib/Cocos2dxActivity”
, “openURL”
, “(Ljava/lang/String;)V”))
{
jstring StringArg1 = t.env~~>NewStringUTF;
t.env~~>CallStaticVoidMethod(t.classID, t.methodID, StringArg1);
}
}

I then modified “Cocos2dxActivity.java” to include this:

public static void openURL(String url) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
startActivity(i);
}

But that had a compiler issue due to startActivity not being static. So I added this:

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Cocos2dxActivity.context = getApplicationContext;

And then changed to this:

public static void openURL(String url) {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(url));
Cocos2dxActivity.context.startActivity;
}

This compiles, but crashes as soon as the button is pressed. I am very unfamiliar with how activities and contexts work within android… Suggestions as to where I am going wrong?

I FINALLY figured it out. For anyone else attempting to do this here is what needs to be changed from above.

Cocos2dxActivity.java:
Add this line to Cocos2dxActivity: private static Activity me = null;
Remove this line from onCreate: Cocos2dxActivity.context = getApplicationContext();
In its place put: me = this;

In openURL remove this line: Cocos2dxActivity.context.startActivity(i);
In its place put: me.startActivity(i);

If anyone wants the full source of the changes, just let me know. Thanks again all for pointing me in the right direction.

Hi, Alan Ide, would you do us a favor to write a tech blog teaching others about this case?

I threw this together this morning. Please take a look, and tell me if it makes sense?

hi Alan Ide

the avove link ‘http://digitalsynapsesblog.blogspot.com/2011/09/cocos2d-x-launching-url-on-android.html’ is broken, where else can i access it?

thanks.

dan

http://digitalsynapsesblog.blogspot.com/

i can not find the Cocos2dJni.h file, i am using cocos2d-x-1.0.1. can i create my own c++ file?

thanks.

thanks for sharing the tutorial Alan Ide,
I try http://digitalsynapsesblog.blogspot.com/ but I got Activity not Found Exception on the Android compiler
any advice ?

thanks

Sorry My Bad I’ve figured out my error was at the url name can’t be “Http://www.”, thanks for this awesome tutorial.

@ dan sui

yes you can create your own c++ file , I’m using following http://www.cocos2d-x.org/boards/10/topics/2595 too as reference

thanks

Thank you for sharing the method, Alan Ide.
But I can not reach “http://digitalsynapsesblog.blogspot.com/”, so is anyone there can repost the tech blog or send the source to my mailbox: mocuishley-work@yahoo.com.cn .
thanks.

The link still works on my end, however since there have been others that are having the same issue accessing the page, here is a copy of it.

Cocos2d-x: Launching a URL on Android...
Since this is my first blog post ever, let me start by introducing myself. My name is Alan, and I am a game programmer, game player, father, son, husband, lover of all things science, and Futurama's/Stargate's biggest fan. I guess thats about it... Now on to the code!


Cocos2d-x is awesome! However, the framework is not designed to do EVERYTHING for you, thus occasionally we have to break out those programming skills, roll our sleeves up, and get dirty. Thankfully, its not very often. I recently found one of these instances when I attempted to open a URL from inside my app while running on an Android device.

Its a bit of a round about process to get from C++ to Java to Android to another App, especially for someone who has been spoiled by how easy it is to do on iOS devices. The very first step is getting your cocos2d-x project up and running on android. I used the following blog post the first time I did it, and it works pretty well.

http://www.supersuraccoon-cocos2d.com/2011/08/10/cocos2d-x-iphone-androidide-installation-and-setup-under-mac-os/

So I assume you have followed the above instructions and made your way back here with a fully functioning "HelloWorld". Let me be the first to congratulate you! What a pain right! Well the good news is that the hard part is over. So lets get back to trying to open a URL on android from inside our cocos2d-x app.

On iOS you would simply call:

[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[NSString stringWithUTF8String:url]]];

However, since we are using the android NDK to interface from our Cocos2d-x project it takes a few more steps. The first step is to open your Eclipse Project you created using the link above. Inside this project you will find "ProjectName/src/org.cocos2dx.lib/Cocos2dxActivity.java". The beginning of that file should look something like this:

/**********Cocos2dxActivity.java**********/
    package org.cocos2dx.lib;

    import android.app.Activity;
    import android.app.AlertDialog;
    import android.app.Dialog;
    import android.content.DialogInterface;
    import android.content.pm.ApplicationInfo;
    import android.content.pm.PackageManager;
    import android.content.pm.PackageManager.NameNotFoundException;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.util.DisplayMetrics;
    import android.util.Log;

    public class Cocos2dxActivity extends Activity{
    public static int screenWidth;
    public static int screenHeight;
    private static Cocos2dxMusic backgroundMusicPlayer;
    private static Cocos2dxSound soundPlayer;
    private static Cocos2dxAccelerometer accelerometer;
    private static boolean accelerometerEnabled = false;
    private static Handler handler;
    private final static int HANDLER_SHOW_DIALOG = 1;
    private static String packageName;

    private static native void nativeSetPaths(String apkPath);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // get frame size
        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        screenWidth = dm.widthPixels;
        screenHeight = dm.heightPixels;
        accelerometer = new Cocos2dxAccelerometer(this);

        // init media player and sound player
        backgroundMusicPlayer = new Cocos2dxMusic(this);
        soundPlayer = new Cocos2dxSound(this);

        handler = new Handler(){
         public void handleMessage(Message msg){
          switch(msg.what){
          case HANDLER_SHOW_DIALOG:
           showDialog(((DialogMessage)msg.obj).title, ((DialogMessage)msg.obj).message);
           break;
          }
         }
        };
    }

    //CLASS METHODS REMOVED FROM EXAMPLE TO SINCE THEY DO NOT CHANGE
}

/********************/


Change the class to look like this. The lines denoted in RED are the changes.


/**********Cocos2dxActivity.java**********/
    package org.cocos2dx.lib;

    import android.app.Activity;
    import android.app.AlertDialog;
    import android.app.Dialog;
    import android.content.DialogInterface;
    import android.content.Intent;
    import android.content.pm.ApplicationInfo;
    import android.content.pm.PackageManager;
    import android.content.pm.PackageManager.NameNotFoundException;
    import android.net.Uri;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.util.DisplayMetrics;
    import android.util.Log;

    public class Cocos2dxActivity extends Activity{
    public static int screenWidth;
    public static int screenHeight;
    private static Cocos2dxMusic backgroundMusicPlayer;
    private static Cocos2dxSound soundPlayer;
    private static Cocos2dxAccelerometer accelerometer;
    private static boolean accelerometerEnabled = false;
    private static Handler handler;
    private final static int HANDLER_SHOW_DIALOG = 1;
    private static String packageName;
    private static Activity me = null;

    private static native void nativeSetPaths(String apkPath);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        me = this;

        // get frame size
        DisplayMetrics dm = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(dm);
        screenWidth = dm.widthPixels;
        screenHeight = dm.heightPixels;
        accelerometer = new Cocos2dxAccelerometer(this);

        // init media player and sound player
        backgroundMusicPlayer = new Cocos2dxMusic(this);
        soundPlayer = new Cocos2dxSound(this);

        handler = new Handler(){
         public void handleMessage(Message msg){
          switch(msg.what){
          case HANDLER_SHOW_DIALOG:
           showDialog(((DialogMessage)msg.obj).title, ((DialogMessage)msg.obj).message);
           break;
          }
         }
        };
    }

    public static void openURL(String url) { 
     Intent i = new Intent(Intent.ACTION_VIEW);  
     i.setData(Uri.parse(url));
     me.startActivity(i);
    }

    //CLASS METHODS REMOVED FROM EXAMPLE TO SINCE THEY DO NOT CHANGE
}

/********************/



Ok... So what did we just do? Well, we created a self reference "me" to the activity created by Cocos2d-x when "onCreate" is called durring the program initialization phase. Then we added a method named "openURL" that will launch a child activity from "me" that sends an intent to the Android OS to parse the URL and open it with the default web browser.

So, why on earth did we go through all of that just to call "startActivity"? Well, thats because "startActivity" is not a static method, and dealing with static methods from the NDK is WAY easier than dealing with non static methods. Thus creating a reference to "me" is the next best thing. Kind of a hack, but an effective one.

So thats all the Java we need to change. Lets move on to the C++ side. Go to your iOS/Android hybrid project root and open "ProjectName/libs/cocos2dx/platform/android/Cocos2dJni.h" and add the following line to the list of method declarations:


void openURLJNI(const char* url); 


Now open "ProjectName/libs/cocos2dx/platform/android/Cocos2dJni.cpp" and add the following method.


void openURLJNI(const char* url)
{
        TMethodJNI t;
        if (getMethodID(t
                        , "org/cocos2dx/lib/Cocos2dxActivity"
                        , "openURL"
                        , "(Ljava/lang/String;)V"))
   {
         jstring StringArg1 = t.env->NewStringUTF(url);
         t.env->CallStaticVoidMethod(t.classID, t.methodID, StringArg1);
   }
}

So what does that do? Well, its just a wrapper for calling the Java function we created earlier from within our C++ project. Now anywhere you want to open a URL you simply call openURLJNI("Http://someplace.com"); Please remember to use the full HTTP:// path when opening your URL since Android is REALLY picky about that.

Obviously that command will NOT work on iOS devices. So my wrapper function looks like this.

void PlatformResolver::openURL(const char* url)
{
#ifdef __APPLE__
    PlatformResolver_OBJC::openURL(url);    
#endif

#ifdef ANDROID
    openURLJNI(url);
#endif
}

Thats it! You can now launch URL links form within your Cocos2d-x application. YAY! Questions, comments, concerns, drop a note below. Thanks!

Thanks Alan Ide, it helps a lot.:slight_smile:

Hi !

There is no Cocos2dJni.h file in the last versions of the Cocos2d-x SDK (.10 and .11). Can anybody explain how to proceed or could it possible to update the tutorial ? Thanks !

Sorry, I have not worked with the newer version of Cocos2D, but I downloaded the source this morning and it looks like they have separated the JNI functions into individual files now. Take a look at “cocos2dx/platform/android/jni” in there you will find a shared “JniHelper class” and a series of JNI based files like MessageJni, and SystemInfoJni. You should be able to create a new .h and .cpp using one of these as a template that will encapsulate the functions I created above.

I assume the Java section is still pretty much the same, but I cannot verify at this time as I do not have eclipse installed on this machine. If you still cant figure it out, please let me know and I will try to port this later this week if I can find the time. Sorry I can’t be more help at the moment. Good luck!

Hey guys… I’ve been new to cocos2d-x… Wanna use jni to send mail in Android. Can any 1 give a step by step tutorial for that?
I know android and cocos2d for iOS. But not able to figure how to connect jni files.

I have it working using the new JNIHelper.

If you follow Alan’s instructions for the java part then do the following.
#1) Create UrlJni.h && UrlJni.cpp in ‘cocos2dx/platform/android/jni’
#2) Make sure to add the .cpp to the make file. (this got in my way for a moment)
cocos2dx/Android.mk : add ‘platform/android/jni/UrlJni.cpp below the other jni methods (’platform/android/jni/TouchesJni.cpp’)
#3) Add this to your UrlJni.h file:

#ifndef __ANDROID_URL_JNI_H__
#define __ANDROID_URL_JNI_H__

extern "C"
{
    extern void openURLJNI(const char* url);
}

#endif

#4) Add this to your UrlJni.cpp file:

#include "UrlJni.h"
#include "JniHelper.h"

#include 

using namespace cocos2d;

extern "C"
{

    void openURLJNI(const char* url)
    {
        JniMethodInfo t;
        if (JniHelper::getStaticMethodInfo(t, "org/cocos2dx/lib/Cocos2dxActivity"
                        ,"openURL"
                        ,"(Ljava/lang/String;)V"))
        {
            jstring StringArg1 = t.env->NewStringUTF(url);
            t.env->CallStaticVoidMethod(t.classID,t.methodID, StringArg1);
        }

    }
}

#5 Now in the class you’re going to call it from add the following lines:

#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)        
#include "platform/android/jni/UrlJni.h"
#endif

...

void AboutScreen::openCompanyWebsite()
{
#if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)        
    openURLJNI("http://www.mycompany.com");
#endif

}

That should do it. If you get an unreferenced error, make sure you did step #2.

Michael Developer &Alan Ide … Thank You so much for your help, I followed your steps and it is working perfectly.
I have some additional questions I wanted to ask. Can I in the same way display dialog alert or toast message of android??
I wrote this code:
public static void openURL(String url) {
Toast.makeText(me,url,Toast.LENGTH_LONG).show();
}

But its crashing with error: Can’t create handler with thread. Do you know how can I display it correctly?
Thanks once again … :slight_smile:

@Swapnil

To be completely honest, I have not touched any android code in many months. (I got to go back to my happy place working with platform agnostic OpenGL/C++). So, I would probably less than helpful in answering this question. Just didnt want you to think I ignored you. Anyone else able to answer it?