PDA

View Full Version : Chartboost SDK v5.0 support for android



darkchazz
09-09-2014, 07:19 PM
Hi,

I'm trying to modify SharedActivity.java to support Chartboost v5.0 per the instructions posted here: https://answers.chartboost.com/hc/en-us/articles/201382919-5-0-Upgrade-Guide-Android

Here is my modified file:


//This is shared between all android p+ projects so be careful!
//Thanks to Phil Hassey for his help and code

package ${PACKAGE_NAME};
import ${PACKAGE_NAME}.SharedMultiTouchInput;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.app.Activity;
import android.os.SystemClock;
import android.text.ClipboardManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Build;
import android.os.Environment;
import java.util.Map;
import java.util.HashMap;
import android.content.DialogInterface;
import android.widget.Button;
import android.widget.TextView;
import android.widget.EditText;
import android.provider.Settings.Secure;
import android.view.ViewGroup;
import android.content.Context;
import android.view.KeyEvent;
import android.view.inputmethod.InputMethodManager;
import android.opengl.GLSurfaceView;
import android.graphics.PixelFormat;

import android.os.Bundle;
import android.os.Vibrator;
import android.view.MotionEvent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import android.util.Log;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundExce ption;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.content.pm.PackageInfo;


//#if defined(RT_GOOGLE_SERVICES_SUPPORT)
import com.google.android.gms.ads.identifier.AdvertisingI dClient;
import com.google.android.gms.ads.identifier.AdvertisingI dClient.Info;
import com.google.android.gms.auth.GooglePlayServicesAvai labilityException;
import com.google.android.gms.common.GooglePlayServicesNo tAvailableException;
import com.google.android.gms.common.GooglePlayServicesRe pairableException;
//#endif

// Wifi
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;

// Licensing Server
import com.android.vending.licensing.AESObfuscator;
import com.android.vending.licensing.LicenseChecker;
import com.android.vending.licensing.LicenseCheckerCallba ck;
import com.android.vending.licensing.ServerManagedPolicy;
import com.android.vending.licensing.StrictPolicy;

import java.util.List;
import java.io.File;
import java.io.FileOutputStream;

import android.media.MediaPlayer;
import android.content.res.AssetFileDescriptor;
import android.media.SoundPool;
import android.media.AudioManager;
import java.util.Locale;

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;

import android.os.Handler;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;

//#if defined(RT_TAPJOY_SUPPORT)

//tapjoy publisher V10.02 supported, the new .jar format, not the old raw source version
import com.tapjoy.TapjoyAwardPointsNotifier;
import com.tapjoy.TapjoyConnect;
import com.tapjoy.TapjoyConnectFlag;
import com.tapjoy.TapjoyConstants;
import com.tapjoy.TapjoyDisplayAdNotifier;
import com.tapjoy.TapjoyEarnedPointsNotifier;
import com.tapjoy.TapjoyFullScreenAdNotifier;
import com.tapjoy.TapjoyLog;
import com.tapjoy.TapjoyNotifier;
import com.tapjoy.TapjoySpendPointsNotifier;
import com.tapjoy.TapjoyVideoNotifier;
import com.tapjoy.TapjoyVideoStatus;
import com.tapjoy.TapjoyViewNotifier;
import com.tapjoy.TapjoyViewType;

//#endif

//#if defined(RT_CHARTBOOST_SUPPORT)
import com.chartboost.sdk.*;
//#endif

//#if defined(RT_FLURRY_SUPPORT)
import com.flurry.android.FlurryAgent;
//#endif

//#if defined(RT_DISABLE_IAP_SUPPORT)
//#else
//Android in app billing
import ${PACKAGE_NAME}.util.IabHelper;
import ${PACKAGE_NAME}.util.IabResult;
import ${PACKAGE_NAME}.util.Inventory;
import ${PACKAGE_NAME}.util.Purchase;
//#endif

//#if defined(RT_HOOKED_SUPPORT)
//for hooked/wasabi
import com.hookedmediagroup.wasabi.WasabiApi;
//#endif

import android.view.View.OnClickListener;

//#if defined(RT_TAPJOY_SUPPORT)
public class SharedActivity extends Activity implements SensorEventListener, TapjoyNotifier, TapjoyFullScreenAdNotifier, TapjoySpendPointsNotifier, TapjoyDisplayAdNotifier, TapjoyAwardPointsNotifier, TapjoyEarnedPointsNotifier, TapjoyVideoNotifier
//#else
public class SharedActivity extends Activity implements SensorEventListener
//#endif
{

//********** THESE WILL BE OVERRIDDEN IN YOUR Main.java file **************
public static String PackageName= "com.rtsoft.something";
public static String dllname= "rtsomething";
public static boolean securityEnabled = false; //if false, it won't try to use the online license stuff
public static boolean bIsShuttingDown = false;
public static boolean IAPEnabled = false; //if false, IAB won't be initted. I call it IAP because I'm used to it from iOS land

public static String tapBannerSize = ""; //tapjoy banner size text, set in Main.cpp, or by AdManager calls
public static int adBannerWidth = 0;
public static int adBannerHeight = 0;

public static String m_iap_asap = "";
public static String m_iap_sync_purchases_asap = "";
public static String m_iap_consume_asap = "";
public static String m_iap_developerdata = "";

public static String m_advertiserID = ""; //only will be set it Google Services is added to the project. This is a big hassle for Proton projects,
//I ended up just copying over the res and jar manually because android.library.reference.1= in project.properties didn't seem to let the manifest access the .res and.. argh.
public static boolean m_limitAdTracking = false;

public static Inventory m_iap_inventory = null;

//#if defined(RT_FLURRY_SUPPORT)
public static String m_flurryAPIKey = "";
//#endif

public static boolean HookedEnabled = false;
//************************************************** **********************
static final int RC_REQUEST = 10001;


public static SharedActivity app = null; //a global to use in static functions with JNI

//for the accelerometer
private static float accelHzSave = 0;
private static Sensor sensor;
private static SensorManager sensorManager;
private static float m_lastMusicVol = 1;
public static int apiVersion;

//TAPJOY

public static View adView;
public static RelativeLayout adLinearLayout;
public static boolean update_display_ad;
public static boolean run_hooked;
public static int tapjoy_ad_show; //0 for don't shot, 1 for show

//#if defined(RT_CHARTBOOST_SUPPORT)
public static boolean cb_cacheInterstitial = false;
public static boolean cb_showInterstitial = false;
// Create the Chartboost object
public static String m_chartBoostAppID = "";
public static String m_chartBoostAppSig = "";
public static boolean cb_performLogon = false;

//#endif

public static boolean set_allow_dimming_asap = false;
public static boolean set_disallow_dimming_asap = false;

//#if defined(RT_DISABLE_IAP_SUPPORT)
//#else
//GOOGLE IAB
// The helper object
IabHelper mHelper;



////////////////////////////////////////////////////////////////////////////
// Licensing Server code
////////////////////////////////////////////////////////////////////////////
public boolean is_demo = false;
public String BASE64_PUBLIC_KEY = "this will be set in your app's Main.java";

//20 random bytes. You can override these in your own Main.java
public byte[] SALT = new byte[] {
24, -96, 16, 91, 65, -86, -54, -73, -101, 12, -84, -90, -53, -68, 20, -67, 45, 35, 85, 17
};

private LicenseCheckerCallback mLicenseCheckerCallback;
private LicenseChecker mChecker;

private class MyLicenseCheckerCallback implements LicenseCheckerCallback
{
public void allow()
{
Log.v("allow()","Allow the user access");

if (isFinishing())
{
// Don't update UI if Activity is finishing.
return;
}
// Should allow user access.
//displayResult(getString(R.string.allow));
}

public void dontAllow()
{
Log.v("dontAllow()","Don't allow the user access");

is_demo = true;

if (isFinishing())
{
// Don't update UI if Activity is finishing.
return;
}
//displayResult(getString(R.string.dont_allow));
// Should not allow access. In most cases, the app should assume
// the user has access unless it encounters this. If it does,
// the app should inform the user of their unlicensed ways
// and then either shut down the app or limit the user to a
// restricted set of features.
// In this example, we show a dialog that takes the user to Market.
showDialog(0);
}

public void applicationError(ApplicationErrorCode errorCode)
{
String result = String.format("Application error: %1$s", errorCode);
Log.v("applicationError",result);

dontAllow();

if (isFinishing())
{
// Don't update UI if Activity is finishing.
return;
}
// This is a polite way of saying the developer made a mistake
// while setting up or calling the license checker library.
// Please examine the error code and fix the error.
//displayResult(result);
}
}

protected Dialog onCreateDialog(int id)
{
// We have only one dialog.
return new AlertDialog.Builder(this)
.setTitle("Application not licensed")
.setMessage("This application is not licensed. Please purchase it from Android Market.\n\nTip: if you have purchased this application, press Retry a few times. It may take a minute to connect to the licensing server. If that does not work, try rebooting your phone.")
.setPositiveButton("Buy app", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
Intent marketIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(
"http://market.android.com/details?id=" + getPackageName()));
startActivity(marketIntent);

app.finish();
android.os.Process.killProcess(android.os.Process. myPid());
}
})
.setNegativeButton("Exit", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{

app.finish();
android.os.Process.killProcess(android.os.Process. myPid());
}
})
.setNeutralButton("Retry", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int which)
{
is_demo = false;
doCheck();
}
})
.create();
}

private void doCheck()
{
//mCheckLicenseButton.setEnabled(false);
//setProgressBarIndeterminateVisibility(true);
//mStatusText.setText(R.string.checking_license);
mChecker.checkAccess(mLicenseCheckerCallback);
}

private void license_init()
{
// Try to use more data here. ANDROID_ID is a single point of attack.
String deviceId = Secure.getString(getContentResolver(), Secure.ANDROID_ID);

// Library calls this when it's done.
mLicenseCheckerCallback = new MyLicenseCheckerCallback();
// Construct the LicenseChecker with a policy.
mChecker = new LicenseChecker(
this,
new ServerManagedPolicy(this,new AESObfuscator(SALT, getPackageName(), deviceId)),
// new StrictPolicy(),
BASE64_PUBLIC_KEY);
doCheck();
}
////////////////////////////////////////////////////////////////////////////
final Handler mMainThreadHandler = new Handler();

@Override
protected void onDestroy()
{
Log.d(PackageName, "Destroying...");

super.onDestroy();
//#if defined(RT_DISABLE_IAP_SUPPORT)
//#else
Log.d(PackageName, "Destroying helper.");
if (mHelper != null) mHelper.dispose();
mHelper = null;
//#endif
}



@Override
public void onStart()
{
super.onStart();
//#if defined(RT_CHARTBOOST_SUPPORT)
Chartboost.onStart(this);
//#endif

}



@Override
public void onStop()
{
super.onStop();

//#if defined(RT_CHARTBOOST_SUPPORT)
Chartboost.onStop(this);
//#endif
}


@Override
public void onBackPressed()
{
//#if defined(RT_CHARTBOOST_SUPPORT)
// If an interstitial is on screen, close it. Otherwise continue as normal.

if (Chartboost.onBackPressed())
return;
else
//#endif

super.onBackPressed();
}

void alert(String message)
{
AlertDialog.Builder bld = new AlertDialog.Builder(this);
bld.setMessage(message);
bld.setNeutralButton("OK", null);
Log.d(PackageName, "Showing alert dialog: " + message);
bld.create().show();
}
void complain(String message)
{
Log.e(PackageName, "Initialization error: " + message);
alert("Error: " + message);
}

@Override
protected void onCreate(Bundle savedInstanceState)
{
app = this;
apiVersion = Build.VERSION.SDK_INT;
Log.d(PackageName, "************************************************** *********************");

Log.d(PackageName, "SDK version: " + apiVersion);

super.onCreate(savedInstanceState);
mGLView = new AppGLSurfaceView(this, this);

setContentView(mGLView);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
if (securityEnabled)
this.license_init();

//#if defined(RT_CHARTBOOST_SUPPORT)

Chartboost.startWithAppId(this, m_chartBoostAppID, m_chartBoostAppSig);


//#endif

//#if defined(RT_TAPJOY_SUPPORT)

//Create dummy tapjoy view overlay we'll show ads on
adLinearLayout = new RelativeLayout(this);
RelativeLayout.LayoutParams l = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PAR ENT, LayoutParams.MATCH_PARENT);
//l.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
Log.d(PackageName, "Tapjoy enabled - setting up adview overlay");
addContentView(adLinearLayout, l);
//#endif

update_display_ad = false;
run_hooked = false;
tapjoy_ad_show = 0;
//#if defined(RT_DISABLE_IAP_SUPPORT)
//#else
if (IAPEnabled)
{
// Log.d(PackageName, "Creating IAB helper.");
mHelper = new IabHelper(this, BASE64_PUBLIC_KEY);

// enable debug logging (for a production application, you should set this to false).
//mHelper.enableDebugLogging(true);

//Log.d(PackageName, "Setting up IAB helper");
mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener()
{
public void onIabSetupFinished(IabResult result)
{
// Log.d(PackageName, "Setup finished.");

if (!result.isSuccess())
{
// Oh noes, there was a problem.
complain("Problem setting up in-app billing (do you have the latest version of the market installed?) : " + result);
//return;
}

// Hooray, IAB is fully set up. Now, let's get an inventory of stuff we own.
//Log.d(PackageName, "Setup successful.");
//mHelper.queryInventoryAsync(mGotInventoryListener) ;
}
});

}
//#endif
sendVersionDetails();



}

// Listener that's called when we finish querying the items and subscriptions we own
IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener()
{
public void onQueryInventoryFinished(IabResult result, Inventory inventory)
{
// Log.d(PackageName, "Query inventory finished.");

// Have we been disposed of in the meantime? If so, quit.
if (mHelper == null) return;


if (result.isFailure())
{
complain("Failed to query inventory: " + result);
SharedActivity.nativeSendGUIEx(SharedActivity.app. MESSAGE_TYPE_IAP_PURCHASED_LIST_STATE, -1,0,0);
return;
}

SharedActivity.app.m_iap_inventory = inventory; //cache for later

// Log.d(PackageName, "Query inventory was successful.");

/*
* Check for items we own. Notice that for each purchase, we check
* the developer payload to see if it's correct! See
* verifyDeveloperPayload().
*/


for(Purchase purchase : inventory.getAllPurchases())
{
//Log.d(PackageName, "Purchase: " + purchase.getSku());
//Log.d(PackageName, "json: " + purchase.getOriginalJson());
nativeSendGUIStringEx(SharedActivity.app.MESSAGE_T YPE_IAP_PURCHASED_LIST_STATE, 0,0,0, purchase.getSku()); //0 means PURCHASED
}

//Log.d(PackageName, "Initial inventory query finished; enabling main UI.");
SharedActivity.nativeSendGUIEx(SharedActivity.app. MESSAGE_TYPE_IAP_PURCHASED_LIST_STATE, -1,0,0); //-1 means END OF LIST

}
};


IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener()
{
public void onIabPurchaseFinished(IabResult result, Purchase purchase)
{
// Log.d(PackageName, "Purchase finished: " + result + ", purchase: " + purchase);

// if we were disposed of in the meantime, quit.
if (mHelper == null) return;

if (result.isFailure())
{
nativeSendGUIEx(MESSAGE_TYPE_IAP_RESULT, result.getResponse() ,0,0);
return;
}

// Log.d(PackageName, "Purchase successful: "+purchase.getOriginalJson());

nativeSendGUIStringEx(MESSAGE_TYPE_IAP_RESULT, result.getResponse(),0,0, purchase.getOriginalJson()+"|"+purchase.getSignature());
}
};


// Called when consumption is complete
IabHelper.OnConsumeFinishedListener mConsumeFinishedListener = new IabHelper.OnConsumeFinishedListener()
{
public void onConsumeFinished(Purchase purchase, IabResult result)
{
// Log.d(PackageName, "Consumption finished. Purchase: " + purchase + ", result: " + result);
// if we were disposed of in the meantime, quit.
if (mHelper == null) return;

if (result.isSuccess())
{
// successfully consumed, so we apply the effects of the item in our
// game world's logic, which in our case means filling the gas tank a bit
Log.d(PackageName, "Consumption successful. Provisioning.");
if (m_iap_inventory != null)
{
m_iap_inventory.erasePurchase(purchase.getSku());
}
}
else
{
complain("Error while consuming: " + result);
}

// Log.d(PackageName, "End consumption flow.");
}
};


@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
// Log.d(PackageName, "****");

// Log.d(PackageName, "onActivityResult(" + requestCode + "," + resultCode + "," + data);

// Pass on the activity result to the helper for handling
if (!mHelper.handleActivityResult(requestCode, resultCode, data))
{
// not handled, so handle it ourselves (here's where you'd
// perform any handling of activity results not related to in-app
// billing...
super.onActivityResult(requestCode, resultCode, data);
}
else
{
// Log.d(PackageName, "onActivityResult handled by IABUtil.");
}
}

//#endif

@Override
protected synchronized void onPause()
{
float hzTemp = accelHzSave;
setup_accel(0); //disable accelerator messages to save on CPU/battery
accelHzSave = hzTemp;
mGLView.onPause();
super.onPause();
//_sounds.autoPause();

//#if defined(RT_FLURRY_SUPPORT)
Log.d(app.PackageName, "Flurry finishing session");
FlurryAgent.onEndSession(app);
//#endif

}

@Override
protected synchronized void onResume()
{
//_sounds.autoResume();

music_set_volume(m_lastMusicVol); //android forgets the audio level, need to reset it
mGLView.onResume();
setup_accel(accelHzSave);
super.onResume();


//#if defined(RT_FLURRY_SUPPORT)
if (!m_flurryAPIKey.isEmpty())
{
Log.d(app.PackageName, "Flurry re-starting session");
FlurryAgent.onStartSession(app, m_flurryAPIKey);
}
//#endif

}

// Create runnable for posting
final Runnable mUpdateMainThread = new Runnable()
{
public void run()
{
if (app.bIsShuttingDown)
{
//Log.d(PackageName, "Finished app in main thread");
app.finish();
//#if defined(RT_DISABLE_IAP_SUPPORT)
//#else
if (IAPEnabled)
{
//this happens in the destroy(), right?
//if (mHelper != null) mHelper.dispose();
//mHelper = null;
}
//#endif
android.os.Process.killProcess(android.os.Process. myPid());
return;
}
updateResultsInUi();
}
};


private void updateResultsInUi()
{
//Log.d(PackageName, "Update");

if (set_allow_dimming_asap)
{
set_allow_dimming_asap = false;
Log.d(app.PackageName, "Allowing screen dimming.");
mGLView.setKeepScreenOn(false);
}

if (set_disallow_dimming_asap)
{
set_allow_dimming_asap = false;
Log.d(app.PackageName, "Disabling screen dimming.");
mGLView.setKeepScreenOn(true);
}

//#if defined(RT_DISABLE_IAP_SUPPORT)
//#else

boolean bWaiting = false;

if (!m_iap_sync_purchases_asap.equals(""))
{
if (!mHelper.isBusy())
{
mHelper.queryInventoryAsync(mGotInventoryListener) ;
m_iap_sync_purchases_asap = ""; //clear it so we don't do it again
} else
{
bWaiting = true;
}
}

if (!m_iap_asap.equals(""))
{
if (!mHelper.isBusy())
{
mHelper.launchPurchaseFlow(app, m_iap_asap, RC_REQUEST, mPurchaseFinishedListener, m_iap_developerdata);
m_iap_asap = ""; //clear it so we don't do it again
m_iap_developerdata = "";
} else
{
bWaiting = true;
}
}

if (!m_iap_consume_asap.equals(""))
{
if (!mHelper.isBusy())
{

if (m_iap_inventory != null)
{
Log.d(app.PackageName, "Initiating consume of"+m_iap_consume_asap);

if (m_iap_inventory.getPurchase(m_iap_consume_asap) == null)
{
Log.d(app.PackageName, "Ignoring consume, customer hasn't bought "+m_iap_consume_asap);
} else
{
mHelper.consumeAsync(m_iap_inventory.getPurchase(m _iap_consume_asap), mConsumeFinishedListener);
}
} else
{
Log.d(app.PackageName, "IAP inventory null, can't consume");
}
m_iap_consume_asap = ""; //clear it so we don't do it again
} else
{
bWaiting = true;
}
}

if (bWaiting)
{
//I have no idea what I'm doing
SharedActivity.app.mMainThreadHandler.post(SharedA ctivity.app.mUpdateMainThread);
}



//#endif

//#if defined(RT_CHARTBOOST_SUPPORT)


if (cb_performLogon)
{
Log.d(app.PackageName, "CB startSession");

Chartboost.onCreate(this);
//Chartboost.startSession();
cb_performLogon = false; //don't want it to happen again
}


if (cb_cacheInterstitial)
{
Log.d(app.PackageName, "Caching CB interstitial");
Chartboost.cacheInterstitial(CBLocation.LOCATION_D EFAULT);
cb_cacheInterstitial = false;
}

if (cb_showInterstitial)
{

Log.d(app.PackageName, "Showing CB interstitial");
//String location = nativeGetLastOSMessageString();
cb_showInterstitial = false;
//ChartBoost _cb = Chartboost.sharedChartboost();
Chartboost.showInterstitial(CBLocation.LOCATION_DE FAULT);
}

//#endif


if (run_hooked && HookedEnabled)
{
Log.d(PackageName, "Lauching Hooked (wasabi) dialog");
run_hooked = false;
//#if defined(RT_HOOKED_SUPPORT)
WasabiApi.handleLogoViewClick(app);
//#endif
}
if (update_display_ad)
{
Log.d(PackageName, "Updating view in main thread");
update_display_ad = false;

adLinearLayout.removeAllViews();

if (tapjoy_ad_show == 1)
{
adLinearLayout.addView(adView);
}
}
}

// JNI used to get Save data dir
public static String get_docdir()
{
File fdir = app.getFilesDir();
return fdir.getAbsolutePath();
}

public static String get_externaldir()
{
//directory of external storage
boolean mExternalStorageAvailable = false;
boolean mExternalStorageWriteable = false;

String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state))
{
mExternalStorageAvailable = mExternalStorageWriteable = true;
}
else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state) )
{
mExternalStorageAvailable = true;
mExternalStorageWriteable = false;
}
else
{
// mExternalStorageAvailable = mExternalStorageWriteable = false;
}

if (mExternalStorageWriteable == false)
return "";

return Environment.getExternalStorageDirectory().toString ();
}

// JNI used to get Save data dir
public static String get_apkFileName()
{
String apkFilePath = null;
ApplicationInfo appInfo = null;
PackageManager packMgmr = app.getPackageManager();
try {
appInfo = packMgmr.getApplicationInfo(PackageName, 0);
}
catch (NameNotFoundException e)
{
e.printStackTrace();
throw new RuntimeException("Unable to locate assets, aborting...");
}
return appInfo.sourceDir;
}

public static String get_region()
{
//will return region in the format "en_us"
Locale l = java.util.Locale.getDefault();
return (l.getLanguage()+"_"+l.getCountry()).toLowerCase();
}

public static int is_app_installed(String packageName)
{
//will return 1 if the app is installed
try
{
ApplicationInfo info = app.getPackageManager().getApplicationInfo(package Name, 0 );
return 1;
} catch( PackageManager.NameNotFoundException e )
{
return 0;
}

}

public static String get_clipboard()
{
//Note: On Honeycomb this appears to cause a crash because it has to be done in the main thread, which isn't active when
//JNI invokes this. So we have to do a callback and send back the answer later? Argh. For now, I'll try/catch the crash, it
//will just be a no-op.

String data = "Thread error, sorry, paste can't be used here.";

try
{
ClipboardManager clipboard = (ClipboardManager) app.getSystemService(CLIPBOARD_SERVICE);
data = clipboard.getText().toString();
}
catch (Exception ex)
{
Log.d(PackageName, "get_clipboard> Avoided crash. "+ex);
}
return data;
}

public static String get_deviceID()
{
String m_szDevIDShort = "35" + //we make this look like a valid IMEI
Build.BOARD.length()%10+ Build.BRAND.length()%10 +
Build.CPU_ABI.length()%10 + Build.DEVICE.length()%10 +
Build.DISPLAY.length()%10 + Build.HOST.length()%10 +
Build.ID.length()%10 + Build.MANUFACTURER.length()%10 +
Build.MODEL.length()%10 + Build.PRODUCT.length()%10 +
Build.TAGS.length()%10 + Build.TYPE.length()%10 +
Build.USER.length()%10 ; //13 digits

if (app.checkCallingOrSelfPermission("android.permission.READ_PHONE_STATE") == PackageManager.PERMISSION_GRANTED)
{
TelephonyManager tm = (TelephonyManager) app.getSystemService(Context.TELEPHONY_SERVICE);
final String DeviceId, SerialNum;
DeviceId = tm.getDeviceId();
SerialNum = tm.getSimSerialNumber();
return m_szDevIDShort + DeviceId + SerialNum;
}
else
{
return m_szDevIDShort;
}
}

public static String get_macAddress()
{
WifiManager wimanager = (WifiManager) app.getSystemService(Context.WIFI_SERVICE);
String macAddress = wimanager.getConnectionInfo().getMacAddress();

if (macAddress == null)
{
macAddress = ""; //blank to signal we couldn't get it
}

return macAddress;
}



public static String get_advertisingIdentifier()
{
return app.m_advertiserID;
}

@Override
public void onSensorChanged(SensorEvent event)
{
switch (event.sensor.getType())
{
case Sensor.TYPE_ACCELEROMETER:
if (event.values.length < 3)
return; //who knows what this is
// Log.d(PackageName, "Accel: " + "x:"+Float.toString(event.values[0]) + " y:"+Float.toString(event.values[1]) + " z:"+Float.toString(event.values[2]));
nativeOnAccelerometerUpdate(event.values[0], event.values[1], event.values[2]);
break;

case Sensor.TYPE_ORIENTATION:
//Log.d(PackageName, "Orientation: " + "x:"+Float.toString(event.values[0]));
break;
}
}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy)
{
//Log.d(PackageName,"onAccuracyChanged: " + sensor + ", accuracy: " + accuracy);
}

public void setup_accel(float hz) //0 to disable
{
accelHzSave = hz;
sensorManager = (SensorManager) app.getSystemService(Context.SENSOR_SERVICE);
sensorManager.unregisterListener(this);

if (hz > 0)
{
sensorManager.registerListener(app, sensorManager.getDefaultSensor(Sensor.TYPE_ACCELER OMETER), sensorManager.SENSOR_DELAY_GAME);
}

//if we ever need orientation data, do this:
//sensorManager.registerListener(app, sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTA TION), sensorManager.SENSOR_DELAY_GAME);
}

// Achievement handler (called from C++ class, and the actual handler is overriden in Main.java to do the actual app specific API call
public void FireAchievement(String achievement)
{
Log.v("Achievement", "Firing in Wrong instance");
}

// JNI to talk to Kiip
public static void HandleAchievement(String achievement)
{
Log.v("Achievement", "Unlocked value: "+achievement);
app.FireAchievement(achievement);
}

/**
* The listener that listen to events from the accelerometer listener
*/

// JNI to open_url
public static void LaunchURL(String url)
{
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
app.startActivity(intent);
}

public static void create_dir_recursively(String basepath, String path)
{
//Log.v("create_dir_recursively", "base: "+basepath+" path: "+path);
String finalPath = basepath+path;
File myDir = new File(finalPath);
myDir.mkdirs();
}

public void toggle_keyboard(boolean show)
{
InputMethodManager mgr = (InputMethodManager)app.getSystemService(Context.I NPUT_METHOD_SERVICE);
mgr.hideSoftInputFromWindow (mGLView.getWindowToken(),0);
if (show)
{
Log.d("Msg","Enabling keyboard");
mgr.toggleSoftInput(InputMethodManager.SHOW_FORCED ,0);
// On the Nexus One, SHOW_FORCED makes it impossible
// to manually dismiss the keyboard.
// On the Droid SHOW_IMPLICIT doesn't bring up the keyboard.
}
else
{
Log.d("Msg", "Disabling keyboard");
}
}

//from MessageManager.h
final static int VIRTUAL_KEY_BACK = 500000;
final static int VIRTUAL_KEY_PROPERTIES = 500001;
final static int VIRTUAL_KEY_HOME = 500002;
final static int VIRTUAL_KEY_SEARCH = 500003;
final static int VIRTUAL_KEY_DIR_UP = 500004;
final static int VIRTUAL_KEY_DIR_DOWN = 500005;
final static int VIRTUAL_KEY_DIR_LEFT = 500006;
final static int VIRTUAL_KEY_DIR_RIGHT = 500007;
final static int VIRTUAL_KEY_DIR_CENTER = 500008;
final static int VIRTUAL_KEY_VOLUME_UP = 500009;
final static int VIRTUAL_KEY_VOLUME_DOWN = 500010;
final static int VIRTUAL_KEY_SHIFT = 500011;
final static int VIRTUAL_KEY_TRACKBALL_DOWN = 500035;
final static int VIRTUAL_DPAD_BUTTON_LEFT = 500036; //square on xperia
final static int VIRTUAL_DPAD_BUTTON_UP = 500037; //triangle on xperia
final static int VIRTUAL_DPAD_BUTTON_RIGHT = 500038; //O
final static int VIRTUAL_DPAD_BUTTON_DOWN = 500039; //X
final static int VIRTUAL_DPAD_SELECT = 500040;
final static int VIRTUAL_DPAD_START = 500041;
final static int VIRTUAL_DPAD_LBUTTON = 500042;
final static int VIRTUAL_DPAD_RBUTTON = 500043;

//messages we could call on Proton using nativeSendGUIEx:
final static int MESSAGE_TYPE_GUI_CLICK_START = 0;
final static int MESSAGE_TYPE_GUI_CLICK_END = 1;
final static int MESSAGE_TYPE_GUI_CLICK_MOVE = 2; //only send when button/finger is held down
final static int MESSAGE_TYPE_GUI_CLICK_MOVE_RAW = 3; //win only, the raw mouse move messages
final static int MESSAGE_TYPE_GUI_ACCELEROMETER = 4;
final static int MESSAGE_TYPE_GUI_TRACKBALL = 5;
final static int MESSAGE_TYPE_GUI_CHAR = 6; //the input box uses it on windows since we don't have a virtual keyboard
final static int MESSAGE_TYPE_GUI_COPY = 7;
final static int MESSAGE_TYPE_GUI_PASTE = 8;
final static int MESSAGE_TYPE_GUI_TOGGLE_FULLSCREEN = 9;

final static int MESSAGE_TYPE_SET_ENTITY_VARIANT = 10;
final static int MESSAGE_TYPE_CALL_ENTITY_FUNCTION = 11;
final static int MESSAGE_TYPE_CALL_COMPONENT_FUNCTION_BY_NAME = 12;
final static int MESSAGE_TYPE_PLAY_SOUND = 13;
final static int MESSAGE_TYPE_VIBRATE = 14;
final static int MESSAGE_TYPE_REMOVE_COMPONENT = 15;
final static int MESSAGE_TYPE_ADD_COMPONENT = 16;
final static int MESSAGE_TYPE_OS_CONNECTION_CHECKED = 17; //sent by macOS, will send an eOSSTreamEvent as parm1
final static int MESSAGE_TYPE_PLAY_MUSIC = 18;
final static int MESSAGE_TYPE_UNKNOWN = 19;
final static int MESSAGE_TYPE_PRELOAD_SOUND = 20;
final static int MESSAGE_TYPE_GUI_CHAR_RAW = 21;
final static int MESSAGE_TYPE_SET_SOUND_ENABLED = 22;

//some tapjoy stuff
final static int MESSAGE_TYPE_TAPJOY_AD_READY = 23;
final static int MESSAGE_TYPE_TAPJOY_FEATURED_APP_READY = 24;
final static int MESSAGE_TYPE_TAPJOY_MOVIE_AD_READY = 25;

//GOOGLE BILLING
final static int MESSAGE_TYPE_IAP_RESULT = 26;
final static int MESSAGE_TYPE_IAP_ITEM_STATE = 27;

//more tapjoy stuff
final static int MESSAGE_TYPE_TAPJOY_TAP_POINTS_RETURN = 28;
final static int MESSAGE_TYPE_TAPJOY_TAP_POINTS_RETURN_ERROR = 29;
final static int MESSAGE_TYPE_TAPJOY_SPEND_TAP_POINTS_RETURN = 30;
final static int MESSAGE_TYPE_TAPJOY_SPEND_TAP_POINTS_RETURN_ERROR = 31;
final static int MESSAGE_TYPE_TAPJOY_AWARD_TAP_POINTS_RETURN = 32;
final static int MESSAGE_TYPE_TAPJOY_AWARD_TAP_POINTS_RETURN_ERROR = 33;
final static int MESSAGE_TYPE_TAPJOY_EARNED_TAP_POINTS = 34;

final static int MESSAGE_TYPE_GUI_JOYPAD_BUTTONS = 35; //For Jake's android gamepad input
final static int MESSAGE_TYPE_GUI_JOYPAD = 36; //For Jake's android gamepad input
final static int MESSAGE_TYPE_GUI_JOYPAD_CONNECT = 37; // For Jakes android gamepad input
final static int MESSAGE_TYPE_CALL_ENTITY_FUNCTION_RECURSIVELY = 38; //used to schedule fake clicks, helps me with debugging

final static int MESSAGE_TYPE_HW_TOUCH_KEYBOARD_WILL_SHOW = 39; //ios only, when not using external keyboard
final static int MESSAGE_TYPE_HW_TOUCH_KEYBOARD_WILL_HIDE = 40; //ios only, when not using external keyboard
final static int MESSAGE_TYPE_HW_KEYBOARD_INPUT_ENDING = 41; //proton is done with input and requesting that the keyboard hid
final static int MESSAGE_TYPE_HW_KEYBOARD_INPUT_STARTING = 42; //proton is asking for the keyboard to open

//GOOGLE BILLING again
final static int MESSAGE_TYPE_IAP_PURCHASED_LIST_STATE = 43; //for sending back lists of items we've already purchased

final static int MESSAGE_TYPE_CALL_STATIC_FUNCTION = 44; // use by other platforms, but this value needs to be reserved by those platforms.

// for sending through version values
final static int MESSAGE_TYPE_APP_VERSION = 45;

final static int MESSAGE_USER = 1000; //send your own messages after this #

//IAP RESPONSE CODES for Proton
final static int RESULT_OK = 0;
final static int RESULT_USER_CANCELED = 1;
final static int RESULT_SERVICE_UNAVAILABLE = 2;
final static int RESULT_BILLING_UNAVAILABLE = 3;
final static int RESULT_ITEM_UNAVAILABLE = 4;
final static int RESULT_DEVELOPER_ERROR = 5;
final static int RESULT_ERROR = 6;
final static int RESULT_OK_ALREADY_PURCHASED = 7;

public int TranslateKeycodeToProtonVirtualKey(int keycode)
{
switch (keycode)
{
case KeyEvent.KEYCODE_BACK:
keycode = VIRTUAL_KEY_BACK;
break;
case KeyEvent.KEYCODE_MENU:
keycode = VIRTUAL_KEY_PROPERTIES;
break;
case KeyEvent.KEYCODE_SEARCH:
keycode = VIRTUAL_KEY_SEARCH;
break;
case KeyEvent.KEYCODE_DPAD_DOWN:
keycode = VIRTUAL_KEY_DIR_DOWN;
break;
case KeyEvent.KEYCODE_DPAD_UP:
keycode = VIRTUAL_KEY_DIR_UP;
break;
case KeyEvent.KEYCODE_DPAD_LEFT:
keycode = VIRTUAL_KEY_DIR_LEFT;
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
keycode = VIRTUAL_KEY_DIR_RIGHT;
break;
case KeyEvent.KEYCODE_DPAD_CENTER:
keycode = VIRTUAL_KEY_DIR_CENTER;
break;
case 0:
keycode = VIRTUAL_KEY_SHIFT;
break;
case KeyEvent.KEYCODE_VOLUME_UP:
keycode = VIRTUAL_KEY_VOLUME_UP;
break;
case KeyEvent.KEYCODE_VOLUME_DOWN:
keycode = VIRTUAL_KEY_VOLUME_DOWN;
break;
}
return keycode;
}
// single touch version, works starting API4/??
public boolean onTrackballEvent (MotionEvent e)
{
if (e.getAction() == MotionEvent.ACTION_MOVE)
{
nativeOnTrackball(e.getX(),e.getY());
//Log.d("Hey", "trackball x rel: "+e.getX()+" y rel: "+e.getY());
return true; //signal that we handled it, so its messages don't show up as normal directional presses
}
else if (e.getAction() == MotionEvent.ACTION_DOWN)
{
//they pushed the button
//Log.d("Hey", "Trackball button pushed");
nativeOnKey(1, VIRTUAL_KEY_TRACKBALL_DOWN, VIRTUAL_KEY_TRACKBALL_DOWN);
}
return false;
}

public boolean onKeyDown(int keycode, KeyEvent e)
{
//Log.v("onKeyDown","Keydown Got "+keycode+" "+Character.toString(Character.toChars(e.getUnicode Char())[0]));

if (e.getRepeatCount() > 0)
return super.onKeyDown(keycode, e);

if(e.isAltPressed() && keycode == KeyEvent.KEYCODE_BACK)
{
//XPeria's O button, not the back button!
//Log.v("onKeyDown","Sending xperia back key");
nativeOnKey(1, VIRTUAL_DPAD_BUTTON_RIGHT, e.getUnicodeChar());
return true; //signal that we handled it
}

//do we want this? Read somewhere it helps with some issues relating to foreign keyboard input..
//if(keycode==KeyEvent.KEYCODE_ALT_LEFT || keycode==KeyEvent.KEYCODE_ALT_RIGHT || keycode==KeyEvent.KEYCODE_SHIFT_LEFT || keycode==KeyEvent.KEYCODE_SHIFT_RIGHT) return true;

switch (keycode)
{
case KeyEvent.KEYCODE_BACK:
{
//#if defined(RT_CHARTBOOST_SUPPORT)
// If an interstitial is on screen, close it. Otherwise continue as normal.
Log.d("onKeyDown","Sending virtual back");
if (Chartboost.onBackPressed())
{
Log.d("onKeyDown","CB handling back");
return true; //ignore the key
}
//#endif

nativeOnKey(1, VIRTUAL_KEY_BACK, e.getUnicodeChar());
return true; //signal that we handled it
}
}

int vKey = TranslateKeycodeToProtonVirtualKey(keycode);
nativeOnKey(1, vKey, (char)e.getUnicodeChar()); //1 means keydown
return super.onKeyDown(keycode, e);
}

public boolean onKeyUp(int keycode, KeyEvent e)
{
//Log.v("onKeyUp","Keyup Got "+keycode+" "+Character.toString(Character.toChars(e.getUnicode Char())[0]));

if(e.isAltPressed() && keycode == KeyEvent.KEYCODE_BACK)
{
//XPeria's O button, not the back button!
//Log.v("onKeyUp","Sending xperia back key");
nativeOnKey(0, VIRTUAL_DPAD_BUTTON_RIGHT, e.getUnicodeChar());
return true; //signal that we handled it
}

switch (keycode)
{
case KeyEvent.KEYCODE_BACK:
{
//#if defined(RT_CHARTBOOST_SUPPORT)
// If an interstitial is on screen, close it. Otherwise continue as normal.
if (Chartboost.onBackPressed())
{
Log.d("onKeyUp","CB handling back");
return true; //ignore the key
}
//#endif

nativeOnKey(0, VIRTUAL_KEY_BACK, e.getUnicodeChar()); //0 is type keyup
return true; //signal that we handled it
}
}

int vKey = TranslateKeycodeToProtonVirtualKey(keycode);

nativeOnKey(0, vKey,(char)e.getUnicodeChar()); //0 is type keyup
return super.onKeyUp(keycode, e);
}

// straight version
public void sendVersionDetails()
{
//Log.v(app.PackageName, "Attempting to send app version");
PackageInfo pInfo;
try {
pInfo = getPackageManager().getPackageInfo(getPackageName( ), 0);
//Log.v(app.PackageName, "Sending version");
nativeSendGUIStringEx(MESSAGE_TYPE_APP_VERSION, 0,0,0, pInfo.versionName);
}
catch (NameNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
Log.d(app.PackageName, "Cannot load App Version!");
}
}


//TAPJOY
//#if defined(RT_TAPJOY_SUPPORT)

// Notifier when a TapjoyConnect.getFullScreenAd is successful.
public void getFullScreenAdResponse()
{
Log.i(app.PackageName, "Displaying Full Screen Ad..");
TapjoyConnect.getTapjoyConnectInstance().showFullS creenAd();
}

// Notifier when a TapjoyConnect.getFullScreenAd request fails.
public void getFullScreenAdResponseFailed(int error)
{
Log.i(app.PackageName, "No Full Screen Ad to display: " + error);

//update_text = true;
//displayText = "No Full Screen Ad to display.";

// We must use a handler since we cannot update UI elements from a different thread.
//mHandler.post(mUpdateResults);
}


public void getDisplayAdResponse(View view)
{
app.adView = view;

int ad_width = app.adBannerWidth;
int ad_height = app.adBannerHeight;

if (ad_width == 0)
ad_width = app.adView.getLayoutParams().width;
if (ad_height == 0)
ad_height = app.adView.getLayoutParams().height;

Log.d(app.PackageName, "adView dimensions: " + ad_width + "x" + ad_height);

int desired_width = app.mGLView.getMeasuredWidth();
Log.d(app.PackageName, "mGLView width is " + desired_width);

if (desired_width > ad_width)
desired_width = ad_width;

// Resize banner to desired width and keep aspect ratio.
RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams(desired_width, (desired_width*ad_height)/ad_width);

//RelativeLayout.LayoutParams l = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PAR ENT, LayoutParams.MATCH_PARENT);
layout.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) ;
layout.addRule(RelativeLayout.CENTER_HORIZONTAL);

app.adView.setLayoutParams(layout);
Log.v(app.PackageName, "adLinearLayout dimensions: " + app.mGLView.getMeasuredWidth() + "x" + app.mGLView.getMeasuredHeight());
nativeSendGUIEx(MESSAGE_TYPE_TAPJOY_AD_READY, (int)1,0,0);
}

public void getDisplayAdResponseFailed(String error)
{
Log.d(app.PackageName, "getDisplayAd error: " + error);

//update_text = true;
//displayText = "Display Ads: " + error;
nativeSendGUIEx(MESSAGE_TYPE_TAPJOY_AD_READY, 0,0,0);

// We must use a handler since we cannot update UI elements from a different thread.
//mMainThreadHandler.post(mUpdateResults);
}

// This method must be implemented if using the TapjoyConnect.getTapPoints() method.
// It is the callback method which contains the currency and points data.
public void getUpdatePoints(String currencyName, int pointTotal)
{
//Log.i("${SMALL_PACKAGE_NAME}", "currencyName: " + currencyName);
//Log.i("${SMALL_PACKAGE_NAME}", "pointTotal: " + pointTotal);
nativeSendGUIStringEx(MESSAGE_TYPE_TAPJOY_TAP_POIN TS_RETURN, pointTotal,0,0, currencyName);
}

// This method must be implemented if using the TapjoyConnect.getTapPoints() method.
// It is the callback method which contains the currency and points data.
public void getUpdatePointsFailed(String error)
{
Log.i("${SMALL_PACKAGE_NAME}", "getTapPoints error: " + error);

nativeSendGUIStringEx(MESSAGE_TYPE_TAPJOY_TAP_POIN TS_RETURN_ERROR, 0,0,0, error);
}

// Notifier for when spending virtual currency succeeds.
public void getSpendPointsResponse(String currencyName, int pointTotal)
{
//Log.i("${SMALL_PACKAGE_NAME}", "currencyName: " + currencyName);
//Log.i("${SMALL_PACKAGE_NAME}", "pointTotal: " + pointTotal);

nativeSendGUIStringEx(MESSAGE_TYPE_TAPJOY_SPEND_TA P_POINTS_RETURN, pointTotal,0,0, currencyName);
}

// Notifier for when spending virtual currency fails.
public void getSpendPointsResponseFailed(String error)
{
Log.i("${SMALL_PACKAGE_NAME}", "spendTapPoints error: " + error);
nativeSendGUIStringEx(MESSAGE_TYPE_TAPJOY_SPEND_TA P_POINTS_RETURN_ERROR, 0,0,0, error);
}

public void getAwardPointsResponse(String currencyName, int pointTotal)
{
//Log.i("${SMALL_PACKAGE_NAME}", "getAwardPointsResponse: " + currencyName);
nativeSendGUIStringEx(MESSAGE_TYPE_TAPJOY_AWARD_TA P_POINTS_RETURN, pointTotal,0,0, currencyName);
}

public void getAwardPointsResponseFailed(String error)
{
Log.i("${SMALL_PACKAGE_NAME}", "getAwardPointsResponseFailed: " + error);
nativeSendGUIStringEx(MESSAGE_TYPE_TAPJOY_AWARD_TA P_POINTS_RETURN_ERROR, 0,0,0, error);
}

public void earnedTapPoints(int amount)
{
//Log.i("${SMALL_PACKAGE_NAME}", "earnedTapPoints: " + amount);
nativeSendGUIStringEx(MESSAGE_TYPE_TAPJOY_EARNED_T AP_POINTS, amount,0,0, "");
}

public void videoReady()
{
Log.i("${SMALL_PACKAGE_NAME}", "VIDEO READY");
nativeSendGUIStringEx(MESSAGE_TYPE_TAPJOY_MOVIE_AD _READY, 1,0,0, "");
}

// Notifier when a video ad starts.
public void videoStart()
{
Log.i("${SMALL_PACKAGE_NAME}", "VIDEO START");
}

public void videoError(int statusCode)
{
String displayText = "Error";

switch (statusCode)
{
case TapjoyVideoStatus.STATUS_MEDIA_STORAGE_UNAVAILABLE :
displayText = "VIDEO ERROR: No SD card or external media storage mounted on device";
break;
case TapjoyVideoStatus.STATUS_NETWORK_ERROR_ON_INIT_VID EOS:
displayText = "VIDEO ERROR: Network error on init videos";
break;
case TapjoyVideoStatus.STATUS_UNABLE_TO_PLAY_VIDEO:
displayText = "VIDEO ERROR: Error playing video";
break;
}

Log.i("${SMALL_PACKAGE_NAME}", "VIDEO ERROR: " + statusCode+" " + displayText);
nativeSendGUIStringEx(MESSAGE_TYPE_TAPJOY_MOVIE_AD _READY, 0,statusCode,0, displayText);
}

public void videoComplete()
{
Log.i("${SMALL_PACKAGE_NAME}", "VIDEO COMPLETE");
nativeSendGUIStringEx(MESSAGE_TYPE_TAPJOY_MOVIE_AD _READY, 2,0,0, "");
}

//#endif
//***************************

//************** SOUND STUFF *************

// JNI to play music, etc
public MediaPlayer _music = null;

private static class MusicFadeOutThread extends Thread {
private final int m_duration;

public MusicFadeOutThread(int duration)
{
super();
m_duration = duration;
}

public void run()
{
final int volumeChangeInterval = 100; // change volume every this amount of ms
final int totalSteps = m_duration / volumeChangeInterval;
int remainingSteps = totalSteps;

while (remainingSteps > 0)
{
synchronized (app._music)
{
float phase = (float)remainingSteps / (float)totalSteps;
app._music.setVolume(phase * m_lastMusicVol, phase * m_lastMusicVol);
remainingSteps--;
}

try {
Thread.sleep(volumeChangeInterval);
}
catch (InterruptedException ie)
{
return;
}
}

synchronized (app._music)
{
app._music.stop();
app._music.setVolume(m_lastMusicVol, m_lastMusicVol);
}
}
}

private MusicFadeOutThread musicFadeOutThread = null;

public synchronized static void music_play(String fname, boolean looping)
{
if (app._music != null)
{
app._music.reset();
}
else
{
app._music = new MediaPlayer();
}

if (fname.charAt(0) == '/')
{
//load as raw, not an asset
try {
File file = new File(fname);
FileInputStream is = new FileInputStream(file);
app._music.setDataSource(is.getFD());
is.close();
app._music.setLooping(looping);
app._music.prepare();
music_set_volume(m_lastMusicVol);
app._music.start();
}
catch(IOException e)
{
Log.d("Can't load music (raw)", fname, e);
}
catch(IllegalStateException e)
{
Log.d("Can't load music (raw), illegal state", fname);
app._music.reset();
}
return;
}

AssetManager am = app.getAssets();
try {
AssetFileDescriptor fd = am.openFd(fname);
app._music.setDataSource(fd.getFileDescriptor(),fd .getStartOffset(),fd.getLength());
fd.close();
app._music.setLooping(looping);
app._music.prepare();
music_set_volume(m_lastMusicVol);
app._music.start();
}
catch(IOException e)
{
Log.d("Can't load music", fname);
}
catch(IllegalStateException e)
{
Log.d("Can't load music, illegal state", fname);
app._music.reset();
}
}

public synchronized static void music_stop()
{
if (app._music == null) { return; }

if (app.musicFadeOutThread != null && app.musicFadeOutThread.isAlive()) {
try {
app.musicFadeOutThread.interrupt();
app.musicFadeOutThread.join();
}
catch (InterruptedException ie)
{
}
}

app._music.stop();
}

public synchronized static void music_fadeout(int duration)
{
if (app._music == null || !app._music.isPlaying())
{
return;
}

if (duration <= 0)
{
music_stop();
}
else if (app.musicFadeOutThread == null || !app.musicFadeOutThread.isAlive())
{
app.musicFadeOutThread = new MusicFadeOutThread(duration);
app.musicFadeOutThread.start();
}
}

public synchronized static void music_set_volume(float v)
{
if (app._music == null)
{
return;
}
m_lastMusicVol = v;
app._music.setVolume(v,v);
}

public synchronized static void vibrate(int vibMS)
{
Vibrator v = (Vibrator) app.getSystemService(Context.VIBRATOR_SERVICE);
v.vibrate(vibMS);
}

public synchronized static int music_get_pos()
{
if (app._music == null)
{
return 0;
}
return app._music.getCurrentPosition();
}

public synchronized static boolean music_is_playing()
{
if (app._music == null)
{
return false;
}
return app._music.isPlaying();
}

public synchronized static void music_set_pos(int positionMS)
{
if (app._music == null)
{
Log.d("warning: music_set_position:", "no music playing, can't set position");
return;
}
app._music.seekTo(positionMS);
}

// JNI to play sounds
public SoundPool _sounds = new SoundPool(8,AudioManager.STREAM_MUSIC,0);

public synchronized static void sound_init()
{
if (app._sounds == null)
{
app._sounds = new SoundPool(8,AudioManager.STREAM_MUSIC,0);
}
}

public synchronized static void sound_destroy()
{
if (app._sounds != null)
{
app._sounds.release();
app._sounds = null;
}
}

public synchronized static int sound_load(String fname)
{
// Log.v("sound_load",fname);

if (fname.charAt(0) == '/')
{
//must be a raw file on the disc, not in the assets. load differently
int sid = app._sounds.load(fname, 1);
return sid;
}

AssetManager am = app.getAssets();
try {
AssetFileDescriptor fd = am.openFd(fname);
int sid = app._sounds.load(fd.getFileDescriptor(),fd.getStar tOffset(),fd.getLength(),1);
return sid;
}
catch(IOException e)
{
Log.d("Can't load sound", fname);
}
return 0;
}

public synchronized static int sound_play(int soundID, float leftVol, float rightVol, int priority, int loop, float speedMod )
{
//Log.v("MSG", "Playing sound: "+soundID);
//Log.v("Sound vol:", ""+leftVol);
return app._sounds.play(soundID,leftVol,rightVol,priority ,loop,speedMod);
}

public static void sound_kill(int soundID )
{
//Log.v("MSG", "killing sound: "+soundID);
app._sounds.unload(soundID);
}

public static void sound_stop(int streamID )
{
//Log.v("MSG", "stopping sound: "+streamID);
//Log.v("Sound vol:", ""+leftVol);
app._sounds.stop(streamID);
}

public static void sound_set_vol(int streamID, float left, float right )
{
//Log.v("MSG", "setting sound vol: "+streamID);
//Log.v("Sound vol:", ""+left);
app._sounds.setVolume(streamID, left, right);
}
public static void sound_set_rate(int streamID, float rate )
{
//Log.v("MSG", "Playing sound: "+soundID);
//Log.v("Sound vol:", ""+leftVol);
app._sounds.setRate(streamID, rate);
}

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

public GLSurfaceView mGLView;
public static native void nativeOnKey(int type, int keycode, int c);
public static native void nativeOnTrackball(float x, float y);
public static native void nativeLaunchURL();
public static native void nativeOnAccelerometerUpdate(float x, float y, float z);
public static native void nativeSendGUIEx(int messageType, int parm1, int parm2, int finger);
public static native void nativeSendGUIStringEx(int messageType, int parm1, int parm2, int finger, String s);
static
{
//System.loadLibrary(dllname);
}
}


class AppGLSurfaceView extends GLSurfaceView
{
public AppGLSurfaceView(Context context, SharedActivity _app)
{
super(context);
app = _app;

//setEGLConfigChooser(8, 8, 8, 8, 16, 0);
//getHolder().setFormat(PixelFormat.TRANSLUCENT);

mRenderer = new AppRenderer(_app);

setRenderer(mRenderer);

/* establish whether the "new" class is available to us */

try
{
WrapSharedMultiTouchInput.checkAvailable(app);
mMultiTouchClassAvailable = true;
} catch (Throwable t)
{
mMultiTouchClassAvailable = false;
}
}

public void onPause()
{
super.onPause();

if (app.bIsShuttingDown == false)
{
nativePause();
}
}

public void onResume()
{
super.onResume();

if (app.bIsShuttingDown == false)
{
nativeResume();
}
}

// single touch version, works starting API4/??
public synchronized boolean onTouchEvent(final MotionEvent e)
{
if (app.is_demo)
{
app.showDialog(0);
}

if (mMultiTouchClassAvailable)
{
return WrapSharedMultiTouchInput.OnInput(e);
}
else
{
float x = e.getX(); float y = e.getY();
int finger = 0; //planning ahead for multi touch
nativeOnTouch(e.getAction(), x,y,finger);
}
return true;
}


AppRenderer mRenderer;

private static native void nativePause();
private static native void nativeResume();
public static native void nativeOnTouch(int action, float x, float y, int finger);
public SharedActivity app;
private static boolean mMultiTouchClassAvailable;
}

class WrapSharedMultiTouchInput
{
private SharedMultiTouchInput mInstance;

/* class initialization fails when this throws an exception */
static
{
try
{
Class.forName("${PACKAGE_NAME}.SharedMultiTouchInput");
}
catch (Exception ex)
{
throw new RuntimeException(ex);
}
}

/* calling here forces class initialization */
public static void checkAvailable(SharedActivity _app)
{
SharedMultiTouchInput.init(_app);
}

public static boolean OnInput(final MotionEvent e)
{
return SharedMultiTouchInput.OnInput(e);
}
}

class AppRenderer implements GLSurfaceView.Renderer
{
public AppRenderer(SharedActivity _app)
{
app = _app;
}

public void onSurfaceCreated(GL10 gl, EGLConfig config)
{

if (app.m_advertiserID == "")
{

//#if defined(RT_GOOGLE_SERVICES_SUPPORT)

//Stuff to get the advertiserID
Thread thr = new Thread(new Runnable() {
@Override
public void run()
{
AdvertisingIdClient.Info adInfo = null;
try {
Context ctx = SharedActivity.app;
adInfo = AdvertisingIdClient.getAdvertisingIdInfo(ctx);

} catch (IllegalStateException e)
{
// Unrecoverable error connecting to Google Play services (e.g.,
// the old version of the service doesn't support getting AdvertisingId).
Log.d(app.PackageName, "IllegalStateException: Unrecoverable error connecting to Google Play services");
} catch (GooglePlayServicesNotAvailableException e)
{
Log.d(app.PackageName, "Google Play services is not available entirely.");
// Google Play services is not available entirely.
} catch (GooglePlayServicesRepairableException e)
{
Log.d(app.PackageName, "GooglePlayServicesRepairableException");
} catch (IOException e)
{
// Unrecoverable error connecting to Google Play services (e.g.,
Log.d(app.PackageName, "Getting AID: IOException");
}

if (adInfo != null)
{
app.m_advertiserID = adInfo.getId();
app.m_limitAdTracking = adInfo.isLimitAdTrackingEnabled();
Log.d(app.PackageName, "------------ Got A-ID: "+app.m_advertiserID+" Tracking: "+app.m_limitAdTracking);

} else
{
Log.d(app.PackageName, "---------- Unable to get A-ID info");
app.m_advertiserID = "";
}
}
});

thr.start();
//#endif
}


}

public void onSurfaceChanged(GL10 gl, int w, int h)
{
//gl.glViewport(0, 0, w, h);
nativeResize(w, h);
nativeInit();
}

//don't change the order of these defines, they match the ones in Proton!

//messages that might be sent to us from Proton's C++ side
final static int MESSAGE_NONE = 0;
final static int MESSAGE_OPEN_TEXT_BOX = 1;
final static int MESSAGE_CLOSE_TEXT_BOX = 2;
final static int MESSAGE_CHECK_CONNECTION = 3;
final static int MESSAGE_SET_FPS_LIMIT = 4;
final static int MESSAGE_SET_ACCELEROMETER_UPDATE_HZ = 5;
final static int MESSAGE_FINISH_APP = 6; //only respected by windows and android right now. webos and iphone don't really need it
final static int MESSAGE_SET_VIDEO_MODE = 7;

final static int MESSAGE_TAPJOY_GET_FEATURED_APP = 8;
final static int MESSAGE_TAPJOY_GET_AD = 9;
final static int MESSAGE_TAPJOY_GET_MOVIE = 10;

final static int MESSAGE_TAPJOY_SHOW_FEATURED_APP = 11;
final static int MESSAGE_TAPJOY_SHOW_AD = 12;
final static int MESSAGE_TAPJOY_SHOW_MOVIE_AD = 13;

final static int MESSAGE_IAP_PURCHASE = 14;
final static int MESSAGE_IAP_GET_PURCHASED_LIST = 15;

final static int MESSAGE_TAPJOY_GET_TAP_POINTS = 16;
final static int MESSAGE_TAPJOY_SPEND_TAP_POINTS = 17;
final static int MESSAGE_TAPJOY_AWARD_TAP_POINTS = 18;
final static int MESSAGE_TAPJOY_SHOW_OFFERS = 19;
final static int MESSAGE_HOOKED_SHOW_RATE_DIALOG = 20;
final static int MESSAGE_ALLOW_SCREEN_DIMMING = 21;
final static int MESSAGE_REQUEST_AD_SIZE = 22;

//CHARTBOOST STUFF

final static int MESSAGE_CHARTBOOST_CACHE_INTERSTITIAL = 23;
final static int MESSAGE_CHARTBOOST_SHOW_INTERSTITIAL = 24;
final static int MESSAGE_CHARTBOOST_CACHE_MORE_APPS = 25;
final static int MESSAGE_CHARTBOOST_SHOW_MORE_APPS = 26;
final static int MESSAGE_CHARTBOOST_SETUP = 27;
final static int MESSAGE_CHARTBOOST_NOTIFY_INSTALL = 28;
final static int MESSAGE_CHARTBOOST_RESERVED1 = 29;
final static int MESSAGE_CHARTBOOST_RESERVED2 = 30;

//FLURRY
final static int MESSAGE_FLURRY_SETUP = 31;
final static int MESSAGE_FLURRY_ON_PAGE_VIEW = 32;
final static int MESSAGE_FLURRY_LOG_EVENT = 33;

final static int MESSAGE_SUSPEND_TO_HOME_SCREEN = 34;

//TJ AGAIN
final static int MESSAGE_TAPJOY_INIT_MAIN = 35;
final static int MESSAGE_TAPJOY_INIT_PAID_APP_WITH_ACTIONID = 36;
final static int MESSAGE_TAPJOY_SET_USERID = 37;

//IAP again
final static int MESSAGE_IAP_CONSUME_ITEM = 38;

static long m_gameTimer = 0;
static int m_timerLoopMS = 0; //every this MS, the loop runs. 0 for no fps limit

public synchronized void onDrawFrame(GL10 gl)
{
if (m_timerLoopMS != 0)
{
while (m_gameTimer > SystemClock.uptimeMillis() || m_gameTimer > SystemClock.uptimeMillis()+m_timerLoopMS+1)
{
//wait a bit - no exception catch needed for the SystemClock version of sleep
SystemClock.sleep(1);
}

m_gameTimer = SystemClock.uptimeMillis()+m_timerLoopMS;
}

if (!app.bIsShuttingDown)
{
nativeUpdate(); //maybe later we'll want to adjust this for performance reasons..
nativeRender();
}

//let's process OS messages sent from the app if any exist
int type = MESSAGE_NONE;

while ( (type = nativeOSMessageGet()) != 0) //it returns 0 if none is available
{
switch (type)
{
case MESSAGE_OPEN_TEXT_BOX: //open text box
app.toggle_keyboard(true);
break;

case MESSAGE_CLOSE_TEXT_BOX: //close text box
app.toggle_keyboard(false);
break;

case MESSAGE_SET_ACCELEROMETER_UPDATE_HZ:
app.setup_accel(nativeGetLastOSMessageX());
break;

case MESSAGE_ALLOW_SCREEN_DIMMING:
if (nativeGetLastOSMessageX() == 0)
{
//disable screen dimming
app.set_disallow_dimming_asap = true; //must do it in the UI thread
app.mMainThreadHandler.post(app.mUpdateMainThread) ;

} else
{
Log.v(app.PackageName, "Allowing screen dimming.");
app.set_allow_dimming_asap = true; //must do it in the UI thread
app.mMainThreadHandler.post(app.mUpdateMainThread) ;

}
break;

case MESSAGE_SET_FPS_LIMIT:
if (nativeGetLastOSMessageX() == 0)
{
//disable it, and avoid a div by 0
m_timerLoopMS = 0;
}
else
{
m_timerLoopMS = (int) (1000.0f/nativeGetLastOSMessageX());
}

//app.setup_accel(nativeGetLastOSMessageX());
break;

case MESSAGE_FINISH_APP:
Log.v(app.PackageName, "Finishing app from java side");
app.bIsShuttingDown = true;
nativeDone();
Log.v(app.PackageName, "Native shutdown");

//app.finish() will get called in the update handler called below, don't need to do it now
app.mMainThreadHandler.post(app.mUpdateMainThread) ;
break;

case MESSAGE_SUSPEND_TO_HOME_SCREEN:
Log.v(app.PackageName, "Suspending to home screen");

Intent i = new Intent();
i.setAction(Intent.ACTION_MAIN);
i.addCategory(Intent.CATEGORY_HOME);
app.startActivity(i);
break;

case MESSAGE_TAPJOY_GET_AD:
//#if defined(RT_TAPJOY_SUPPORT)
Log.v(app.PackageName, "banner ads no longer supported in TJ 10");
//TapjoyConnect.getTapjoyConnectInstance().setBanner AdSize(app.tapBannerSize);
TapjoyConnect.getTapjoyConnectInstance().getDispla yAd(app);
//#else
Log.v(app.PackageName, "ERROR: RT_TAPJOY_SUPPORT isn't defined in Java project, you can't use it!");
//#endif
break;



case MESSAGE_FLURRY_SETUP:
{
//#if defined(RT_FLURRY_SUPPORT)

app.m_flurryAPIKey = nativeGetLastOSMessageString();
//Log.v(app.PackageName, "Setting up flurry: "+app.m_flurryAPIKey);
FlurryAgent.onStartSession(app, app.m_flurryAPIKey);
//#else
Log.v(app.PackageName, "ERROR: RT_FLURRY_SUPPORT isn't defined in Main.java, you can't use it!");
//#endif
}
break;

//#if defined(RT_FLURRY_SUPPORT)


case MESSAGE_FLURRY_ON_PAGE_VIEW:
{
//Log.v(app.PackageName, "MESSAGE_FLURRY_ON_PAGE_VIEW> Incrementing page view");
FlurryAgent.onPageView();
}
break;

case MESSAGE_FLURRY_LOG_EVENT:
{
String event = nativeGetLastOSMessageString();
String key = nativeGetLastOSMessageString2();
String data = nativeGetLastOSMessageString3();

if (!key.isEmpty())
{
//Log.v(app.PackageName, "MESSAGE_FLURRY_LOG_EVENT: Event + key/data: "+event+" key: "+key+", data: "+data);

Map<String,String> m=new HashMap<String, String>();
m.put(key, data);
FlurryAgent.logEvent(event, m, true);
}
else
{
//Log.v(app.PackageName, "MESSAGE_FLURRY_LOG_EVENT: Event: "+event);
FlurryAgent.logEvent(event, true);
}
}
break;


//#endif
case MESSAGE_CHARTBOOST_SETUP:
{
//#if defined(RT_CHARTBOOST_SUPPORT)

//ChartBoost stuff - we can't initialize it here, it must be done in the GUI thread. So
//we'll sort of just set some flags, causing it to happen next pass, before any other CB
//stuff.

app.m_chartBoostAppID = nativeGetLastOSMessageString();
app.m_chartBoostAppSig = nativeGetLastOSMessageString2();
app.cb_performLogon = true; //so it triggers
//#else
Log.v(app.PackageName, "ERROR: RT_CHARTBOOST_SUPPORT isn't defined in Main.java, you can't use it!");
//#endif
}
break;

//#if defined(RT_CHARTBOOST_SUPPORT)

case MESSAGE_CHARTBOOST_CACHE_INTERSTITIAL:
{
//must wait and do it in UI thread
app.cb_cacheInterstitial = true;
app.mMainThreadHandler.post(app.mUpdateMainThread) ;
}
break;

case MESSAGE_CHARTBOOST_SHOW_INTERSTITIAL:
{
//must wait and do it in UI thread
app.cb_showInterstitial = true;
app.mMainThreadHandler.post(app.mUpdateMainThread) ;

}
break;

//TODO: Handle the other messages...

//#endif


//#if defined(RT_TAPJOY_SUPPORT)

case MESSAGE_TAPJOY_SET_USERID:
Log.v(app.PackageName, "Setting userID: " +nativeGetLastOSMessageString());
TapjoyConnect.getTapjoyConnectInstance().setUserID (nativeGetLastOSMessageString());
break;


case MESSAGE_TAPJOY_GET_FEATURED_APP:
//re-purposing to show videos
Log.v(app.PackageName, "Asking tj for fullscreen ad");
//TapjoyConnect.getTapjoyConnectInstance().getFeatur edApp(app);
if (nativeGetLastOSMessageString().length() > 0)
{
TapjoyConnect.getTapjoyConnectInstance().getFullSc reenAdWithCurrencyID(nativeGetLastOSMessageString( ), app);
} else
{
TapjoyConnect.getTapjoyConnectInstance().getFullSc reenAd(app);
}
break;

case MESSAGE_TAPJOY_SHOW_FEATURED_APP:
//Log.v(app.PackageName, "show tapjoy ap");
TapjoyConnect.getTapjoyConnectInstance().showFullS creenAd();
break;

case MESSAGE_TAPJOY_GET_TAP_POINTS:
//Log.v(app.PackageName, "Getting tapjoy points");
TapjoyConnect.getTapjoyConnectInstance().getTapPoi nts(app);
break;

case MESSAGE_TAPJOY_SPEND_TAP_POINTS:
//Log.v(app.PackageName, "Spending tappoints: " + nativeGetLastOSMessageParm1());
TapjoyConnect.getTapjoyConnectInstance().spendTapP oints(nativeGetLastOSMessageParm1(), app);
break;

case MESSAGE_TAPJOY_AWARD_TAP_POINTS:
TapjoyConnect.getTapjoyConnectInstance().awardTapP oints(nativeGetLastOSMessageParm1(), app);
break;

case MESSAGE_TAPJOY_SHOW_OFFERS:
// This will show Offers web view from where you can download the latest offers.
// Note: If you want to provide your own publisher id then use following method to show offer web view:
// TapjoyOffers.getTapjoyOffersInstance().showOffers( this, "provide here publisher id");
TapjoyConnect.getTapjoyConnectInstance().showOffer s();
break;

case MESSAGE_TAPJOY_SHOW_AD:

app.tapjoy_ad_show = (int)nativeGetLastOSMessageX();
Log.v(app.PackageName, "Tapjoy banner ads no longer supported in SDK 10, parm is: " + app.tapjoy_ad_show);
//TapjoyConnect.getTapjoyConnectInstance().enableBan nerAdAutoRefresh(app.tapjoy_ad_show != 0);

app.update_display_ad = true;
// We must use a handler since we cannot update UI elements from a different thread.
app.mMainThreadHandler.post(app.mUpdateMainThread) ;
break;

case MESSAGE_REQUEST_AD_SIZE:
app.adBannerWidth = (int)nativeGetLastOSMessageX();
app.adBannerHeight = (int)nativeGetLastOSMessageY();

app.adBannerWidth = 480;
app.adBannerHeight = 72;

app.tapBannerSize = app.adBannerWidth+"x"+app.adBannerHeight;
Log.v(app.PackageName, "Setting tapjoy banner size to " + app.tapBannerSize);
break;

//#endif
//#if defined(RT_DISABLE_IAP_SUPPORT)
//#else
case MESSAGE_IAP_PURCHASE:

String parm = nativeGetLastOSMessageString();
String payload = nativeGetLastOSMessageString2();

//Log.v(app.PackageName, "Buying "+parm);

if (!SharedActivity.IAPEnabled)
{
Log.d(app.PackageName, "requestPurchase>> Um, you'll need to change IAPEnabled to true in Main.java!");

} else
{
app.m_iap_asap = parm;
app.m_iap_developerdata = payload;

app.mMainThreadHandler.post(app.mUpdateMainThread) ;


/*
if (!app.mBillingService.requestPurchase(parm, ""))
{
Log.d(app.PackageName, "requestPurchase>> Billing not supported?!");
}
*/
//SharedActivity.nativeSendGUIEx(app.MESSAGE_TYPE_IA P_RESULT, ResponseCode.RESULT_BILLING_UNAVAILABLE.ordinal(), 0,0);
}
break;

case MESSAGE_IAP_GET_PURCHASED_LIST:

if (!SharedActivity.IAPEnabled)
{
Log.d(app.PackageName, "requestPurchase>> Um, you'll need to change IAPEnabled to true in Main.java!");

} else
{
// Log.d(app.PackageName, "Get purchased list");
app.m_iap_sync_purchases_asap = "true";
app.mMainThreadHandler.post(app.mUpdateMainThread) ;

}
break;

case MESSAGE_IAP_CONSUME_ITEM:
Log.d(app.PackageName, "Consume");
app.m_iap_consume_asap = nativeGetLastOSMessageString();
app.mMainThreadHandler.post(app.mUpdateMainThread) ;

break;
//#endif
case MESSAGE_HOOKED_SHOW_RATE_DIALOG:
Log.v(app.PackageName, "Launching hooked");
app.run_hooked = true;
app.mMainThreadHandler.post(app.mUpdateMainThread) ;
break;

default:
Log.v("Unhandled","Unhandled OS message");
}
}
}

private static native void nativeInit();
private static native void nativeResize(int w, int h);
private static native void nativeUpdate();
private static native void nativeRender();
private static native void nativeDone();

//yes, I probably should do this as a Java class and init it from C++ and send that over but..

private static native int nativeOSMessageGet();
private static native int nativeGetLastOSMessageParm1();
private static native float nativeGetLastOSMessageX();
private static native float nativeGetLastOSMessageY();
private static native String nativeGetLastOSMessageString();
private static native String nativeGetLastOSMessageString2();
private static native String nativeGetLastOSMessageString3();
public SharedActivity app;
}




It compiles but Ads do not appear.
Help please?