summaryrefslogtreecommitdiff
path: root/platform/android/java/src/org/godotengine
diff options
context:
space:
mode:
authorJuan Linietsky <reduzio@gmail.com>2016-01-08 17:53:00 -0300
committerJuan Linietsky <reduzio@gmail.com>2016-01-08 17:53:00 -0300
commit40ba22631bbb7fc4c6b88d01402e132dbaceaf2b (patch)
tree4629c457d390ba7c7eef4c3d31ebb803fc8179ce /platform/android/java/src/org/godotengine
parent401622cc229317bd218f070dd07a3bd8db582f16 (diff)
Renamed godot domain from com.android.godot (which was incorrect) to org.godotengine.godot
Diffstat (limited to 'platform/android/java/src/org/godotengine')
-rw-r--r--platform/android/java/src/org/godotengine/godot/Dictionary.java80
-rw-r--r--platform/android/java/src/org/godotengine/godot/Godot.java925
-rw-r--r--platform/android/java/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java30
-rw-r--r--platform/android/java/src/org/godotengine/godot/GodotDownloaderService.java56
-rw-r--r--platform/android/java/src/org/godotengine/godot/GodotIO.java673
-rw-r--r--platform/android/java/src/org/godotengine/godot/GodotLib.java66
-rw-r--r--platform/android/java/src/org/godotengine/godot/GodotPaymentV3.java154
-rw-r--r--platform/android/java/src/org/godotengine/godot/GodotView.java661
-rw-r--r--platform/android/java/src/org/godotengine/godot/input/GodotEditText.java133
-rw-r--r--platform/android/java/src/org/godotengine/godot/input/GodotTextInputWrapper.java154
-rw-r--r--platform/android/java/src/org/godotengine/godot/payments/ConsumeTask.java71
-rw-r--r--platform/android/java/src/org/godotengine/godot/payments/GenericConsumeTask.java53
-rw-r--r--platform/android/java/src/org/godotengine/godot/payments/HandlePurchaseTask.java82
-rw-r--r--platform/android/java/src/org/godotengine/godot/payments/PaymentsCache.java45
-rw-r--r--platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java282
-rw-r--r--platform/android/java/src/org/godotengine/godot/payments/PurchaseTask.java101
-rw-r--r--platform/android/java/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java87
-rw-r--r--platform/android/java/src/org/godotengine/godot/payments/ValidateTask.java97
-rw-r--r--platform/android/java/src/org/godotengine/godot/utils/Crypt.java39
-rw-r--r--platform/android/java/src/org/godotengine/godot/utils/CustomSSLSocketFactory.java54
-rw-r--r--platform/android/java/src/org/godotengine/godot/utils/HttpRequester.java206
-rw-r--r--platform/android/java/src/org/godotengine/godot/utils/RequestParams.java58
22 files changed, 4107 insertions, 0 deletions
diff --git a/platform/android/java/src/org/godotengine/godot/Dictionary.java b/platform/android/java/src/org/godotengine/godot/Dictionary.java
new file mode 100644
index 0000000000..34051c4bb8
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/Dictionary.java
@@ -0,0 +1,80 @@
+/*************************************************************************/
+/* Dictionary.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+package org.godotengine.godot;
+
+import java.util.HashMap;
+import java.util.Set;
+
+
+public class Dictionary extends HashMap<String, Object> {
+
+ protected String[] keys_cache;
+
+ public String[] get_keys() {
+
+ String[] ret = new String[size()];
+ int i=0;
+ Set<String> keys = keySet();
+ for (String key : keys) {
+
+ ret[i] = key;
+ i++;
+ };
+
+ return ret;
+ };
+
+ public Object[] get_values() {
+
+ Object[] ret = new Object[size()];
+ int i=0;
+ Set<String> keys = keySet();
+ for (String key : keys) {
+
+ ret[i] = get(key);
+ i++;
+ };
+
+ return ret;
+ };
+
+ public void set_keys(String[] keys) {
+ keys_cache = keys;
+ };
+
+ public void set_values(Object[] vals) {
+
+ int i=0;
+ for (String key : keys_cache) {
+ put(key, vals[i]);
+ i++;
+ };
+ keys_cache = null;
+ };
+};
diff --git a/platform/android/java/src/org/godotengine/godot/Godot.java b/platform/android/java/src/org/godotengine/godot/Godot.java
new file mode 100644
index 0000000000..6aa5d24f1c
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/Godot.java
@@ -0,0 +1,925 @@
+/*************************************************************************/
+/* Godot.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2015 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+package org.godotengine.godot;
+
+import android.R;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.view.ViewGroup.LayoutParams;
+import android.app.*;
+import android.content.*;
+import android.content.SharedPreferences.Editor;
+import android.view.*;
+import android.view.inputmethod.InputMethodManager;
+import android.os.*;
+import android.util.Log;
+import android.graphics.*;
+import android.text.method.*;
+import android.text.*;
+import android.media.*;
+import android.hardware.*;
+import android.content.*;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.Uri;
+import android.media.MediaPlayer;
+
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.ArrayList;
+
+import org.godotengine.godot.payments.PaymentsManager;
+
+import java.io.IOException;
+
+import android.provider.Settings.Secure;
+import android.widget.FrameLayout;
+
+import org.godotengine.godot.input.*;
+
+import java.io.InputStream;
+import javax.microedition.khronos.opengles.GL10;
+import java.security.MessageDigest;
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.LinkedList;
+
+import com.google.android.vending.expansion.downloader.Constants;
+import com.google.android.vending.expansion.downloader.DownloadProgressInfo;
+import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
+import com.google.android.vending.expansion.downloader.DownloaderServiceMarshaller;
+import com.google.android.vending.expansion.downloader.Helpers;
+import com.google.android.vending.expansion.downloader.IDownloaderClient;
+import com.google.android.vending.expansion.downloader.IDownloaderService;
+import com.google.android.vending.expansion.downloader.IStub;
+
+import android.os.Bundle;
+import android.os.Messenger;
+import android.os.SystemClock;
+
+
+public class Godot extends Activity implements SensorEventListener, IDownloaderClient
+{
+
+ static final int MAX_SINGLETONS = 64;
+ private IStub mDownloaderClientStub;
+ private IDownloaderService mRemoteService;
+ private TextView mStatusText;
+ private TextView mProgressFraction;
+ private TextView mProgressPercent;
+ private TextView mAverageSpeed;
+ private TextView mTimeRemaining;
+ private ProgressBar mPB;
+
+ private View mDashboard;
+ private View mCellMessage;
+
+ private Button mPauseButton;
+ private Button mWiFiSettingsButton;
+
+ private boolean use_32_bits=false;
+ private boolean use_immersive=false;
+ private boolean mStatePaused;
+ private int mState;
+
+ private void setState(int newState) {
+ if (mState != newState) {
+ mState = newState;
+ mStatusText.setText(Helpers.getDownloaderStringResourceIDFromState(newState));
+ }
+ }
+
+ private void setButtonPausedState(boolean paused) {
+ mStatePaused = paused;
+ int stringResourceID = paused ? com.godot.game.R.string.text_button_resume :
+ com.godot.game.R.string.text_button_pause;
+ mPauseButton.setText(stringResourceID);
+ }
+
+ static public class SingletonBase {
+
+ protected void registerClass(String p_name, String[] p_methods) {
+
+ GodotLib.singleton(p_name,this);
+
+ Class clazz = getClass();
+ Method[] methods = clazz.getDeclaredMethods();
+ for (Method method : methods) {
+ boolean found=false;
+ Log.d("XXX","METHOD: %s\n" + method.getName());
+
+ for (String s : p_methods) {
+ Log.d("XXX", "METHOD CMP WITH: %s\n" + s);
+ if (s.equals(method.getName())) {
+ found=true;
+ Log.d("XXX","METHOD CMP VALID");
+ break;
+ }
+ }
+ if (!found)
+ continue;
+
+ Log.d("XXX","METHOD FOUND: %s\n" + method.getName());
+
+ List<String> ptr = new ArrayList<String>();
+
+ Class[] paramTypes = method.getParameterTypes();
+ for (Class c : paramTypes) {
+ ptr.add(c.getName());
+ }
+
+ String[] pt = new String[ptr.size()];
+ ptr.toArray(pt);
+
+ GodotLib.method(p_name,method.getName(),method.getReturnType().getName(),pt);
+
+
+ }
+
+ Godot.singletons[Godot.singleton_count++]=this;
+ }
+
+ protected void onMainActivityResult(int requestCode, int resultCode, Intent data) {
+
+
+ }
+
+ protected void onMainPause() {}
+ protected void onMainResume() {}
+ protected void onMainDestroy() {}
+
+ protected void onGLDrawFrame(GL10 gl) {}
+ protected void onGLSurfaceChanged(GL10 gl, int width, int height) {} // singletons will always miss first onGLSurfaceChanged call
+ //protected void onGLSurfaceCreated(GL10 gl, EGLConfig config) {} // singletons won't be ready until first GodotLib.step()
+
+ public void registerMethods() {}
+ }
+
+/*
+ protected List<SingletonBase> singletons = new ArrayList<SingletonBase>();
+ protected void instanceSingleton(SingletonBase s) {
+
+ s.registerMethods();
+ singletons.add(s);
+ }
+
+*/
+
+ private String[] command_line;
+
+ public GodotView mView;
+ private boolean godot_initialized=false;
+
+
+ private SensorManager mSensorManager;
+ private Sensor mAccelerometer;
+
+ public FrameLayout layout;
+ public RelativeLayout adLayout;
+
+
+ static public GodotIO io;
+
+ public static void setWindowTitle(String title) {
+ //setTitle(title);
+ }
+
+
+ static SingletonBase singletons[] = new SingletonBase[MAX_SINGLETONS];
+ static int singleton_count=0;
+
+
+ public interface ResultCallback {
+ public void callback(int requestCode, int resultCode, Intent data);
+ };
+ public ResultCallback result_callback;
+
+ private PaymentsManager mPaymentsManager = null;
+
+ @Override protected void onActivityResult (int requestCode, int resultCode, Intent data) {
+ if(requestCode == PaymentsManager.REQUEST_CODE_FOR_PURCHASE){
+ mPaymentsManager.processPurchaseResponse(resultCode, data);
+ }else if (result_callback != null) {
+ result_callback.callback(requestCode, resultCode, data);
+ result_callback = null;
+ };
+
+ for(int i=0;i<singleton_count;i++) {
+
+ singletons[i].onMainActivityResult(requestCode,resultCode,data);
+ }
+ };
+
+ public void onVideoInit(boolean use_gl2) {
+
+// mView = new GodotView(getApplication(),io,use_gl2);
+// setContentView(mView);
+
+ layout = new FrameLayout(this);
+ layout.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));
+ setContentView(layout);
+
+ // GodotEditText layout
+ GodotEditText edittext = new GodotEditText(this);
+ edittext.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.WRAP_CONTENT));
+ // ...add to FrameLayout
+ layout.addView(edittext);
+
+ mView = new GodotView(getApplication(),io,use_gl2,use_32_bits, this);
+ layout.addView(mView,new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));
+ mView.setKeepScreenOn(true);
+
+ edittext.setView(mView);
+ io.setEdit(edittext);
+
+ // Ad layout
+ adLayout = new RelativeLayout(this);
+ adLayout.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT,LayoutParams.FILL_PARENT));
+ layout.addView(adLayout);
+
+ }
+
+ private static Godot _self;
+
+ public static Godot getInstance(){
+ return Godot._self;
+ }
+
+
+ private String[] getCommandLine() {
+ InputStream is;
+ try {
+ is = getAssets().open("_cl_");
+ byte[] len = new byte[4];
+ int r = is.read(len);
+ if (r<4) {
+ Log.d("XXX","**ERROR** Wrong cmdline length.\n");
+ Log.d("GODOT", "**ERROR** Wrong cmdline length.\n");
+ return new String[0];
+ }
+ int argc=((int)(len[3]&0xFF)<<24) | ((int)(len[2]&0xFF)<<16) | ((int)(len[1]&0xFF)<<8) | ((int)(len[0]&0xFF));
+ String[] cmdline = new String[argc];
+
+ for(int i=0;i<argc;i++) {
+ r = is.read(len);
+ if (r<4) {
+
+ Log.d("GODOT", "**ERROR** Wrong cmdline param lenght.\n");
+ return new String[0];
+ }
+ int strlen=((int)(len[3]&0xFF)<<24) | ((int)(len[2]&0xFF)<<16) | ((int)(len[1]&0xFF)<<8) | ((int)(len[0]&0xFF));
+ if (strlen>65535) {
+ Log.d("GODOT", "**ERROR** Wrong command len\n");
+ return new String[0];
+ }
+ byte[] arg = new byte[strlen];
+ r = is.read(arg);
+ if (r==strlen) {
+ cmdline[i]=new String(arg,"UTF-8");
+ }
+ }
+ return cmdline;
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.d("GODOT", "**ERROR** Exception " + e.getClass().getName() + ":" + e.getMessage());
+ return new String[0];
+ }
+
+
+ }
+
+
+ String expansion_pack_path;
+
+
+ private void initializeGodot() {
+
+ if (expansion_pack_path!=null) {
+
+ String[] new_cmdline;
+ int cll=0;
+ if (command_line!=null) {
+ Log.d("GODOT", "initializeGodot: command_line: is not null" );
+ new_cmdline = new String[ command_line.length + 2 ];
+ cll=command_line.length;
+ for(int i=0;i<command_line.length;i++) {
+ new_cmdline[i]=command_line[i];
+ }
+ } else {
+ Log.d("GODOT", "initializeGodot: command_line: is null" );
+ new_cmdline = new String[ 2 ];
+ }
+
+ new_cmdline[cll]="-main_pack";
+ new_cmdline[cll+1]=expansion_pack_path;
+ command_line=new_cmdline;
+ }
+
+ io = new GodotIO(this);
+ io.unique_id = Secure.getString(getContentResolver(), Secure.ANDROID_ID);
+ GodotLib.io=io;
+ Log.d("GODOT", "command_line is null? " + ((command_line == null)?"yes":"no"));
+ /*if(command_line != null){
+ Log.d("GODOT", "Command Line:");
+ for(int w=0;w <command_line.length;w++){
+ Log.d("GODOT"," " + command_line[w]);
+ }
+ }*/
+ GodotLib.initialize(this,io.needsReloadHooks(),command_line,getAssets());
+ mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
+ mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME);
+
+ result_callback = null;
+
+ mPaymentsManager = PaymentsManager.createManager(this).initService();
+ godot_initialized=true;
+
+ }
+
+ @Override
+ public void onServiceConnected(Messenger m) {
+ mRemoteService = DownloaderServiceMarshaller.CreateProxy(m);
+ mRemoteService.onClientUpdated(mDownloaderClientStub.getMessenger());
+ }
+
+
+
+ @Override
+ protected void onCreate(Bundle icicle) {
+
+ Log.d("GODOT", "** GODOT ACTIVITY CREATED HERE ***\n");
+
+ super.onCreate(icicle);
+ _self = this;
+ Window window = getWindow();
+ window.addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+
+ //check for apk expansion API
+ if (true) {
+ boolean md5mismatch = false;
+ command_line = getCommandLine();
+ boolean use_apk_expansion=false;
+ String main_pack_md5=null;
+ String main_pack_key=null;
+
+ List<String> new_args = new LinkedList<String>();
+
+
+ for(int i=0;i<command_line.length;i++) {
+
+ boolean has_extra = i< command_line.length -1;
+ if (command_line[i].equals("-use_depth_32")) {
+ use_32_bits=true;
+ } else if (command_line[i].equals("-use_immersive")) {
+ use_immersive=true;
+ if(Build.VERSION.SDK_INT >= 19.0){ // check if the application runs on an android 4.4+
+ window.getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
+ | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+
+ UiChangeListener();
+ }
+ } else if (command_line[i].equals("-use_apk_expansion")) {
+ use_apk_expansion=true;
+ } else if (has_extra && command_line[i].equals("-apk_expansion_md5")) {
+ main_pack_md5=command_line[i+1];
+ i++;
+ } else if (has_extra && command_line[i].equals("-apk_expansion_key")) {
+ main_pack_key=command_line[i+1];
+ SharedPreferences prefs = getSharedPreferences("app_data_keys", MODE_PRIVATE);
+ Editor editor = prefs.edit();
+ editor.putString("store_public_key", main_pack_key);
+
+ editor.commit();
+ i++;
+ } else if (command_line[i].trim().length()!=0){
+ new_args.add(command_line[i]);
+ }
+ }
+
+ if (new_args.isEmpty()){
+ command_line=null;
+ }else{
+
+ command_line = new_args.toArray(new String[new_args.size()]);
+ }
+ if (use_apk_expansion && main_pack_md5!=null && main_pack_key!=null) {
+ //check that environment is ok!
+ if (!Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED )) {
+ Log.d("GODOT", "**ERROR! No media mounted!");
+ //show popup and die
+ }
+
+ // Build the full path to the app's expansion files
+ try {
+ expansion_pack_path = Environment.getExternalStorageDirectory().toString() + "/Android/obb/"+this.getPackageName();
+ expansion_pack_path+="/"+"main."+getPackageManager().getPackageInfo(getPackageName(), 0).versionCode+"."+this.getPackageName()+".obb";
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ File f = new File(expansion_pack_path);
+
+ boolean pack_valid = true;
+ Log.d("GODOT","**PACK** - Path "+expansion_pack_path);
+
+ if (!f.exists()) {
+
+ pack_valid=false;
+ Log.d("GODOT","**PACK** - File does not exist");
+
+ } else if( obbIsCorrupted(expansion_pack_path, main_pack_md5)){
+ Log.d("GODOT", "**PACK** - Expansion pack (obb) is corrupted");
+ pack_valid = false;
+ try{
+ f.delete();
+ }catch(Exception e){
+ Log.d("GODOT", "**PACK** - Error deleting corrupted expansion pack (obb)");
+ }
+ }
+
+ if (!pack_valid) {
+ Log.d("GODOT", "Pack Invalid, try re-downloading.");
+
+ Intent notifierIntent = new Intent(this, this.getClass());
+ notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+ Intent.FLAG_ACTIVITY_CLEAR_TOP);
+
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
+ notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ int startResult;
+ try {
+ Log.d("GODOT", "INITIALIZING DOWNLOAD");
+ startResult = DownloaderClientMarshaller.startDownloadServiceIfRequired(
+ getApplicationContext(),
+ pendingIntent,
+ GodotDownloaderService.class);
+ Log.d("GODOT", "DOWNLOAD SERVICE FINISHED:" + startResult);
+
+ if (startResult != DownloaderClientMarshaller.NO_DOWNLOAD_REQUIRED) {
+ Log.d("GODOT", "DOWNLOAD REQUIRED");
+ // This is where you do set up to display the download
+ // progress (next step)
+ mDownloaderClientStub = DownloaderClientMarshaller.CreateStub(this,
+ GodotDownloaderService.class);
+
+ setContentView(com.godot.game.R.layout.downloading_expansion);
+ mPB = (ProgressBar) findViewById(com.godot.game.R.id.progressBar);
+ mStatusText = (TextView) findViewById(com.godot.game.R.id.statusText);
+ mProgressFraction = (TextView) findViewById(com.godot.game.R.id.progressAsFraction);
+ mProgressPercent = (TextView) findViewById(com.godot.game.R.id.progressAsPercentage);
+ mAverageSpeed = (TextView) findViewById(com.godot.game.R.id.progressAverageSpeed);
+ mTimeRemaining = (TextView) findViewById(com.godot.game.R.id.progressTimeRemaining);
+ mDashboard = findViewById(com.godot.game.R.id.downloaderDashboard);
+ mCellMessage = findViewById(com.godot.game.R.id.approveCellular);
+ mPauseButton = (Button) findViewById(com.godot.game.R.id.pauseButton);
+ mWiFiSettingsButton = (Button) findViewById(com.godot.game.R.id.wifiSettingsButton);
+
+ return;
+ } else{
+ Log.d("GODOT", "NO DOWNLOAD REQUIRED");
+ }
+ } catch (NameNotFoundException e) {
+ // TODO Auto-generated catch block
+ Log.d("GODOT", "Error downloading expansion package:" + e.getMessage());
+ }
+
+ }
+
+ }
+ }
+
+ initializeGodot();
+
+
+ // instanceSingleton( new GodotFacebook(this) );
+
+
+ }
+
+
+ @Override protected void onDestroy(){
+
+ if(mPaymentsManager != null ) mPaymentsManager.destroy();
+ for(int i=0;i<singleton_count;i++) {
+ singletons[i].onMainDestroy();
+ }
+ super.onDestroy();
+ }
+
+ @Override protected void onPause() {
+ super.onPause();
+ if (!godot_initialized){
+ if (null != mDownloaderClientStub) {
+ mDownloaderClientStub.disconnect(this);
+ }
+ return;
+ }
+ mView.onPause();
+ mSensorManager.unregisterListener(this);
+ GodotLib.focusout();
+
+ for(int i=0;i<singleton_count;i++) {
+ singletons[i].onMainPause();
+ }
+ }
+
+ @Override protected void onResume() {
+ super.onResume();
+ if (!godot_initialized){
+ if (null != mDownloaderClientStub) {
+ mDownloaderClientStub.connect(this);
+ }
+ return;
+ }
+
+ mView.onResume();
+ mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
+ GodotLib.focusin();
+ if(use_immersive && Build.VERSION.SDK_INT >= 19.0){ // check if the application runs on an android 4.4+
+ Window window = getWindow();
+ window.getDecorView().setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
+ | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ }
+
+ for(int i=0;i<singleton_count;i++) {
+
+ singletons[i].onMainResume();
+ }
+
+
+
+ }
+
+ public void UiChangeListener() {
+ final View decorView = getWindow().getDecorView();
+ decorView.setOnSystemUiVisibilityChangeListener (new View.OnSystemUiVisibilityChangeListener() {
+ @Override
+ public void onSystemUiVisibilityChange(int visibility) {
+ if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
+ decorView.setSystemUiVisibility(
+ View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
+ }
+ }
+ });
+ }
+
+ @Override public void onSensorChanged(SensorEvent event) {
+ Display display = ((WindowManager) getSystemService(WINDOW_SERVICE)).getDefaultDisplay();
+ int displayRotation = display.getRotation();
+
+ float[] adjustedValues = new float[3];
+ final int axisSwap[][] = {
+ { 1, -1, 0, 1 }, // ROTATION_0
+ {-1, -1, 1, 0 }, // ROTATION_90
+ {-1, 1, 0, 1 }, // ROTATION_180
+ { 1, 1, 1, 0 } }; // ROTATION_270
+
+ final int[] as = axisSwap[displayRotation];
+ adjustedValues[0] = (float)as[0] * event.values[ as[2] ];
+ adjustedValues[1] = (float)as[1] * event.values[ as[3] ];
+ adjustedValues[2] = event.values[2];
+
+ float x = adjustedValues[0];
+ float y = adjustedValues[1];
+ float z = adjustedValues[2];
+ GodotLib.accelerometer(x,y,z);
+ }
+
+ @Override public final void onAccuracyChanged(Sensor sensor, int accuracy) {
+ // Do something here if sensor accuracy changes.
+ }
+
+/*
+ @Override public boolean dispatchKeyEvent(KeyEvent event) {
+
+ if (event.getKeyCode()==KeyEvent.KEYCODE_BACK) {
+
+ System.out.printf("** BACK REQUEST!\n");
+
+ GodotLib.quit();
+ return true;
+ }
+ System.out.printf("** OTHER KEY!\n");
+
+ return false;
+ }
+*/
+
+ @Override public void onBackPressed() {
+
+ System.out.printf("** BACK REQUEST!\n");
+ GodotLib.quit();
+ }
+
+ public void forceQuit() {
+
+ System.exit(0);
+ }
+
+
+
+ private boolean obbIsCorrupted(String f, String main_pack_md5){
+
+ try {
+
+ InputStream fis = new FileInputStream(f);
+
+ // Create MD5 Hash
+ byte[] buffer = new byte[16384];
+
+ MessageDigest complete = MessageDigest.getInstance("MD5");
+ int numRead;
+ do {
+ numRead = fis.read(buffer);
+ if (numRead > 0) {
+ complete.update(buffer, 0, numRead);
+ }
+ } while (numRead != -1);
+
+
+ fis.close();
+ byte[] messageDigest = complete.digest();
+
+ // Create Hex String
+ StringBuffer hexString = new StringBuffer();
+ for (int i=0; i<messageDigest.length; i++) {
+ String s = Integer.toHexString(0xFF & messageDigest[i]);
+
+ if (s.length()==1) {
+ s="0"+s;
+ }
+ hexString.append(s);
+ }
+ String md5str = hexString.toString();
+
+ //Log.d("GODOT","**PACK** - My MD5: "+hexString+" - APK md5: "+main_pack_md5);
+ if (!md5str.equals(main_pack_md5)) {
+ Log.d("GODOT","**PACK MD5 MISMATCH???** - MD5 Found: "+md5str+" "+Integer.toString(md5str.length())+" - MD5 Expected: "+main_pack_md5+" "+Integer.toString(main_pack_md5.length()));
+ return true;
+ }
+ return false;
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.d("GODOT","**PACK FAIL**");
+ return true;
+ }
+ }
+
+ //@Override public boolean dispatchTouchEvent (MotionEvent event) {
+ public boolean gotTouchEvent(MotionEvent event) {
+
+ super.onTouchEvent(event);
+ int evcount=event.getPointerCount();
+ if (evcount==0)
+ return true;
+
+ int[] arr = new int[event.getPointerCount()*3];
+
+ for(int i=0;i<event.getPointerCount();i++) {
+
+ arr[i*3+0]=(int)event.getPointerId(i);
+ arr[i*3+1]=(int)event.getX(i);
+ arr[i*3+2]=(int)event.getY(i);
+ }
+
+ //System.out.printf("gaction: %d\n",event.getAction());
+ switch(event.getAction()&MotionEvent.ACTION_MASK) {
+
+ case MotionEvent.ACTION_DOWN: {
+ GodotLib.touch(0,0,evcount,arr);
+ //System.out.printf("action down at: %f,%f\n", event.getX(),event.getY());
+ } break;
+ case MotionEvent.ACTION_MOVE: {
+ GodotLib.touch(1,0,evcount,arr);
+ //for(int i=0;i<event.getPointerCount();i++) {
+ // System.out.printf("%d - moved to: %f,%f\n",i, event.getX(i),event.getY(i));
+ //}
+ } break;
+ case MotionEvent.ACTION_POINTER_UP: {
+ final int indexPointUp = event.getActionIndex();
+ final int pointer_idx = event.getPointerId(indexPointUp);
+ GodotLib.touch(4,pointer_idx,evcount,arr);
+ //System.out.printf("%d - s.up at: %f,%f\n",pointer_idx, event.getX(pointer_idx),event.getY(pointer_idx));
+ } break;
+ case MotionEvent.ACTION_POINTER_DOWN: {
+ int pointer_idx = event.getActionIndex();
+ GodotLib.touch(3,pointer_idx,evcount,arr);
+ //System.out.printf("%d - s.down at: %f,%f\n",pointer_idx, event.getX(pointer_idx),event.getY(pointer_idx));
+ } break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP: {
+ GodotLib.touch(2,0,evcount,arr);
+ //for(int i=0;i<event.getPointerCount();i++) {
+ // System.out.printf("%d - up! %f,%f\n",i, event.getX(i),event.getY(i));
+ //}
+ } break;
+
+ }
+ return true;
+ }
+
+ @Override public boolean onKeyMultiple(final int inKeyCode, int repeatCount, KeyEvent event) {
+ String s = event.getCharacters();
+ if (s == null || s.length() == 0)
+ return super.onKeyMultiple(inKeyCode, repeatCount, event);
+
+ final char[] cc = s.toCharArray();
+ int cnt = 0;
+ for (int i = cc.length; --i >= 0; cnt += cc[i] != 0 ? 1 : 0);
+ if (cnt == 0) return super.onKeyMultiple(inKeyCode, repeatCount, event);
+ final Activity me = this;
+ queueEvent(new Runnable() {
+ // This method will be called on the rendering thread:
+ public void run() {
+ for (int i = 0, n = cc.length; i < n; i++) {
+ int keyCode;
+ if ((keyCode = cc[i]) != 0) {
+ // Simulate key down and up...
+ GodotLib.key(0, keyCode, true);
+ GodotLib.key(0, keyCode, false);
+ }
+ }
+ }
+ });
+ return true;
+ }
+
+ private void queueEvent(Runnable runnable) {
+ // TODO Auto-generated method stub
+
+ }
+
+ public PaymentsManager getPaymentsManager() {
+ return mPaymentsManager;
+ }
+
+// public void setPaymentsManager(PaymentsManager mPaymentsManager) {
+// this.mPaymentsManager = mPaymentsManager;
+// };
+
+
+ // Audio
+
+ /**
+ * The download state should trigger changes in the UI --- it may be useful
+ * to show the state as being indeterminate at times. This sample can be
+ * considered a guideline.
+ */
+ @Override
+ public void onDownloadStateChanged(int newState) {
+ Log.d("GODOT", "onDownloadStateChanged:" + newState);
+ setState(newState);
+ boolean showDashboard = true;
+ boolean showCellMessage = false;
+ boolean paused;
+ boolean indeterminate;
+ switch (newState) {
+ case IDownloaderClient.STATE_IDLE:
+ Log.d("GODOT", "STATE IDLE");
+ // STATE_IDLE means the service is listening, so it's
+ // safe to start making calls via mRemoteService.
+ paused = false;
+ indeterminate = true;
+ break;
+ case IDownloaderClient.STATE_CONNECTING:
+ case IDownloaderClient.STATE_FETCHING_URL:
+ Log.d("GODOT", "STATE CONNECTION / FETCHING URL");
+ showDashboard = true;
+ paused = false;
+ indeterminate = true;
+ break;
+ case IDownloaderClient.STATE_DOWNLOADING:
+ Log.d("GODOT", "STATE DOWNLOADING");
+ paused = false;
+ showDashboard = true;
+ indeterminate = false;
+ break;
+
+ case IDownloaderClient.STATE_FAILED_CANCELED:
+ case IDownloaderClient.STATE_FAILED:
+ case IDownloaderClient.STATE_FAILED_FETCHING_URL:
+ case IDownloaderClient.STATE_FAILED_UNLICENSED:
+ Log.d("GODOT", "MANY TYPES OF FAILING");
+ paused = true;
+ showDashboard = false;
+ indeterminate = false;
+ break;
+ case IDownloaderClient.STATE_PAUSED_NEED_CELLULAR_PERMISSION:
+ case IDownloaderClient.STATE_PAUSED_WIFI_DISABLED_NEED_CELLULAR_PERMISSION:
+ Log.d("GODOT", "PAUSED FOR SOME STUPID REASON");
+ showDashboard = false;
+ paused = true;
+ indeterminate = false;
+ showCellMessage = true;
+ break;
+
+ case IDownloaderClient.STATE_PAUSED_BY_REQUEST:
+ Log.d("GODOT", "PAUSED BY STUPID USER");
+ paused = true;
+ indeterminate = false;
+ break;
+ case IDownloaderClient.STATE_PAUSED_ROAMING:
+ case IDownloaderClient.STATE_PAUSED_SDCARD_UNAVAILABLE:
+ Log.d("GODOT", "PAUSED BY ROAMING WTF!?");
+ paused = true;
+ indeterminate = false;
+ break;
+ case IDownloaderClient.STATE_COMPLETED:
+ Log.d("GODOT", "COMPLETED");
+ showDashboard = false;
+ paused = false;
+ indeterminate = false;
+// validateXAPKZipFiles();
+ initializeGodot();
+ return;
+ default:
+ Log.d("GODOT", "DEFAULT ????");
+ paused = true;
+ indeterminate = true;
+ showDashboard = true;
+ }
+ int newDashboardVisibility = showDashboard ? View.VISIBLE : View.GONE;
+ if (mDashboard.getVisibility() != newDashboardVisibility) {
+ mDashboard.setVisibility(newDashboardVisibility);
+ }
+ int cellMessageVisibility = showCellMessage ? View.VISIBLE : View.GONE;
+ if (mCellMessage.getVisibility() != cellMessageVisibility) {
+ mCellMessage.setVisibility(cellMessageVisibility);
+ }
+
+ mPB.setIndeterminate(indeterminate);
+ setButtonPausedState(paused);
+ }
+
+
+ @Override
+ public void onDownloadProgress(DownloadProgressInfo progress) {
+ mAverageSpeed.setText(getString(com.godot.game.R.string.kilobytes_per_second,
+ Helpers.getSpeedString(progress.mCurrentSpeed)));
+ mTimeRemaining.setText(getString(com.godot.game.R.string.time_remaining,
+ Helpers.getTimeRemaining(progress.mTimeRemaining)));
+
+ progress.mOverallTotal = progress.mOverallTotal;
+ mPB.setMax((int) (progress.mOverallTotal >> 8));
+ mPB.setProgress((int) (progress.mOverallProgress >> 8));
+ mProgressPercent.setText(Long.toString(progress.mOverallProgress
+ * 100 /
+ progress.mOverallTotal) + "%");
+ mProgressFraction.setText(Helpers.getDownloadProgressString
+ (progress.mOverallProgress,
+ progress.mOverallTotal));
+
+ }
+
+}
diff --git a/platform/android/java/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java b/platform/android/java/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java
new file mode 100644
index 0000000000..b602f4757c
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/GodotDownloaderAlarmReceiver.java
@@ -0,0 +1,30 @@
+package org.godotengine.godot;
+
+import com.google.android.vending.expansion.downloader.DownloaderClientMarshaller;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Log;
+
+/**
+ * You should start your derived downloader class when this receiver gets the message
+ * from the alarm service using the provided service helper function within the
+ * DownloaderClientMarshaller. This class must be then registered in your AndroidManifest.xml
+ * file with a section like this:
+ * <receiver android:name=".GodotDownloaderAlarmReceiver"/>
+ */
+public class GodotDownloaderAlarmReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ Log.d("GODOT", "Alarma recivida");
+ try {
+ DownloaderClientMarshaller.startDownloadServiceIfRequired(context, intent, GodotDownloaderService.class);
+ } catch (NameNotFoundException e) {
+ e.printStackTrace();
+ Log.d("GODOT", "Exception: " + e.getClass().getName() + ":" + e.getMessage());
+ }
+ }
+}
diff --git a/platform/android/java/src/org/godotengine/godot/GodotDownloaderService.java b/platform/android/java/src/org/godotengine/godot/GodotDownloaderService.java
new file mode 100644
index 0000000000..6735d387f3
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/GodotDownloaderService.java
@@ -0,0 +1,56 @@
+package org.godotengine.godot;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+import com.google.android.vending.expansion.downloader.impl.DownloaderService;
+
+/**
+ * This class demonstrates the minimal client implementation of the
+ * DownloaderService from the Downloader library.
+ */
+public class GodotDownloaderService extends DownloaderService {
+ // stuff for LVL -- MODIFY FOR YOUR APPLICATION!
+ private static final String BASE64_PUBLIC_KEY = "REPLACE THIS WITH YOUR PUBLIC KEY";
+ // used by the preference obfuscater
+ private static final byte[] SALT = new byte[] {
+ 1, 43, -12, -1, 54, 98,
+ -100, -12, 43, 2, -8, -4, 9, 5, -106, -108, -33, 45, -1, 84
+ };
+
+ /**
+ * This public key comes from your Android Market publisher account, and it
+ * used by the LVL to validate responses from Market on your behalf.
+ */
+ @Override
+ public String getPublicKey() {
+ SharedPreferences prefs = getApplicationContext().getSharedPreferences("app_data_keys", Context.MODE_PRIVATE);
+ Log.d("GODOT", "getting public key:" + prefs.getString("store_public_key", null));
+ return prefs.getString("store_public_key", null);
+
+// return BASE64_PUBLIC_KEY;
+ }
+
+ /**
+ * This is used by the preference obfuscater to make sure that your
+ * obfuscated preferences are different than the ones used by other
+ * applications.
+ */
+ @Override
+ public byte[] getSALT() {
+ return SALT;
+ }
+
+ /**
+ * Fill this in with the class name for your alarm receiver. We do this
+ * because receivers must be unique across all of Android (it's a good idea
+ * to make sure that your receiver is in your unique package)
+ */
+ @Override
+ public String getAlarmReceiverClassName() {
+ Log.d("GODOT", "getAlarmReceiverClassName()");
+ return GodotDownloaderAlarmReceiver.class.getName();
+ }
+
+}
diff --git a/platform/android/java/src/org/godotengine/godot/GodotIO.java b/platform/android/java/src/org/godotengine/godot/GodotIO.java
new file mode 100644
index 0000000000..071d090e8b
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/GodotIO.java
@@ -0,0 +1,673 @@
+/*************************************************************************/
+/* GodotIO.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2015 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+package org.godotengine.godot;
+import java.util.HashMap;
+import java.util.Locale;
+import android.net.Uri;
+import android.content.Intent;
+import android.content.res.AssetManager;
+import java.io.InputStream;
+import java.io.IOException;
+import android.app.*;
+import android.content.*;
+import android.view.*;
+import android.view.inputmethod.InputMethodManager;
+import android.os.*;
+import android.util.Log;
+import android.graphics.*;
+import android.text.method.*;
+import android.text.*;
+import android.media.*;
+import android.hardware.*;
+import android.content.*;
+import android.content.pm.ActivityInfo;
+import org.godotengine.godot.input.*;
+//android.os.Build
+
+// Wrapper for native library
+
+public class GodotIO {
+
+
+ AssetManager am;
+ Godot activity;
+ GodotEditText edit;
+
+ Context applicationContext;
+ MediaPlayer mediaPlayer;
+
+ final int SCREEN_LANDSCAPE=0;
+ final int SCREEN_PORTRAIT=1;
+ final int SCREEN_REVERSE_LANDSCAPE=2;
+ final int SCREEN_REVERSE_PORTRAIT=3;
+ final int SCREEN_SENSOR_LANDSCAPE=4;
+ final int SCREEN_SENSOR_PORTRAIT=5;
+ final int SCREEN_SENSOR=6;
+
+ /////////////////////////
+ /// FILES
+ /////////////////////////
+
+ public int last_file_id=1;
+
+ class AssetData {
+
+
+ public boolean eof=false;
+ public String path;
+ public InputStream is;
+ public int len;
+ public int pos;
+ }
+
+
+ HashMap<Integer,AssetData> streams;
+
+
+ public int file_open(String path,boolean write) {
+
+ //System.out.printf("file_open: Attempt to Open %s\n",path);
+
+ //Log.v("MyApp", "TRYING TO OPEN FILE: " + path);
+ if (write)
+ return -1;
+
+
+ AssetData ad = new AssetData();
+
+ try {
+ ad.is = am.open(path);
+
+ } catch (Exception e) {
+
+ //System.out.printf("Exception on file_open: %s\n",path);
+ return -1;
+ }
+
+ try {
+ ad.len=ad.is.available();
+ } catch (Exception e) {
+
+ System.out.printf("Exception availabling on file_open: %s\n",path);
+ return -1;
+ }
+
+ ad.path=path;
+ ad.pos=0;
+ ++last_file_id;
+ streams.put(last_file_id,ad);
+
+ return last_file_id;
+ }
+ public int file_get_size(int id) {
+
+ if (!streams.containsKey(id)) {
+ System.out.printf("file_get_size: Invalid file id: %d\n",id);
+ return -1;
+ }
+
+ return streams.get(id).len;
+
+ }
+ public void file_seek(int id,int bytes) {
+
+ if (!streams.containsKey(id)) {
+ System.out.printf("file_get_size: Invalid file id: %d\n",id);
+ return;
+ }
+ //seek sucks
+ AssetData ad = streams.get(id);
+ if (bytes>ad.len)
+ bytes=ad.len;
+ if (bytes<0)
+ bytes=0;
+
+ try {
+
+ if (bytes > (int)ad.pos) {
+ int todo=bytes-(int)ad.pos;
+ while(todo>0) {
+ todo-=ad.is.skip(todo);
+ }
+ ad.pos=bytes;
+ } else if (bytes<(int)ad.pos) {
+
+ ad.is=am.open(ad.path);
+
+ ad.pos=bytes;
+ int todo=bytes;
+ while(todo>0) {
+ todo-=ad.is.skip(todo);
+ }
+ }
+
+ ad.eof=false;
+ } catch (IOException e) {
+
+ System.out.printf("Exception on file_seek: %s\n",e);
+ return;
+ }
+
+
+ }
+
+ public int file_tell(int id) {
+
+ if (!streams.containsKey(id)) {
+ System.out.printf("file_read: Can't tell eof for invalid file id: %d\n",id);
+ return 0;
+ }
+
+ AssetData ad = streams.get(id);
+ return ad.pos;
+ }
+ public boolean file_eof(int id) {
+
+ if (!streams.containsKey(id)) {
+ System.out.printf("file_read: Can't check eof for invalid file id: %d\n",id);
+ return false;
+ }
+
+ AssetData ad = streams.get(id);
+ return ad.eof;
+ }
+
+ public byte[] file_read(int id, int bytes) {
+
+ if (!streams.containsKey(id)) {
+ System.out.printf("file_read: Can't read invalid file id: %d\n",id);
+ return new byte[0];
+ }
+
+
+ AssetData ad = streams.get(id);
+
+ if (ad.pos + bytes > ad.len) {
+
+ bytes=ad.len-ad.pos;
+ ad.eof=true;
+ }
+
+
+ if (bytes==0) {
+
+ return new byte[0];
+ }
+
+
+
+ byte[] buf1=new byte[bytes];
+ int r=0;
+ try {
+ r = ad.is.read(buf1);
+ } catch (IOException e) {
+
+ System.out.printf("Exception on file_read: %s\n",e);
+ return new byte[bytes];
+ }
+
+ if (r==0) {
+ return new byte[0];
+ }
+
+ ad.pos+=r;
+
+ if (r<bytes) {
+
+ byte[] buf2=new byte[r];
+ for(int i=0;i<r;i++)
+ buf2[i]=buf1[i];
+ return buf2;
+ } else {
+
+ return buf1;
+ }
+
+ }
+
+ public void file_close(int id) {
+
+ if (!streams.containsKey(id)) {
+ System.out.printf("file_close: Can't close invalid file id: %d\n",id);
+ return;
+ }
+
+ streams.remove(id);
+
+ }
+
+
+ /////////////////////////
+ /// DIRECTORIES
+ /////////////////////////
+
+
+ class AssetDir {
+
+ public String[] files;
+ public int current;
+ public String path;
+ }
+
+ public int last_dir_id=1;
+
+ HashMap<Integer,AssetDir> dirs;
+
+ public int dir_open(String path) {
+
+ AssetDir ad = new AssetDir();
+ ad.current=0;
+ ad.path=path;
+
+ try {
+ ad.files = am.list(path);
+ } catch (IOException e) {
+
+ System.out.printf("Exception on dir_open: %s\n",e);
+ return -1;
+ }
+
+ //System.out.printf("Opened dir: %s\n",path);
+ ++last_dir_id;
+ dirs.put(last_dir_id,ad);
+
+ return last_dir_id;
+
+ }
+
+ public boolean dir_is_dir(int id) {
+ if (!dirs.containsKey(id)) {
+ System.out.printf("dir_next: invalid dir id: %d\n",id);
+ return false;
+ }
+ AssetDir ad = dirs.get(id);
+ //System.out.printf("go next: %d,%d\n",ad.current,ad.files.length);
+ int idx = ad.current;
+ if (idx>0)
+ idx--;
+
+ if (idx>=ad.files.length)
+ return false;
+ String fname = ad.files[idx];
+
+ try {
+ if (ad.path.equals(""))
+ am.open(fname);
+ else
+ am.open(ad.path+"/"+fname);
+ return false;
+ } catch (Exception e) {
+ return true;
+ }
+ }
+
+ public String dir_next(int id) {
+
+ if (!dirs.containsKey(id)) {
+ System.out.printf("dir_next: invalid dir id: %d\n",id);
+ return "";
+ }
+
+ AssetDir ad = dirs.get(id);
+ //System.out.printf("go next: %d,%d\n",ad.current,ad.files.length);
+
+ if (ad.current>=ad.files.length) {
+ ad.current++;
+ return "";
+ }
+ String r = ad.files[ad.current];
+ ad.current++;
+ return r;
+
+ }
+
+ public void dir_close(int id) {
+
+ if (!dirs.containsKey(id)) {
+ System.out.printf("dir_close: invalid dir id: %d\n",id);
+ return;
+ }
+
+ dirs.remove(id);
+ }
+
+
+
+ GodotIO(Godot p_activity) {
+
+ am=p_activity.getAssets();
+ activity=p_activity;
+ streams=new HashMap<Integer,AssetData>();
+ dirs=new HashMap<Integer,AssetDir>();
+ applicationContext = activity.getApplicationContext();
+
+ }
+
+
+ /////////////////////////
+ // AUDIO
+ /////////////////////////
+
+ private Object buf;
+ private Thread mAudioThread;
+ private AudioTrack mAudioTrack;
+
+ public Object audioInit(int sampleRate, int desiredFrames) {
+ int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_STEREO;
+ int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
+ int frameSize = 4;
+
+ System.out.printf("audioInit: initializing audio:\n");
+
+ //Log.v("Godot", "Godot audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + ((float)sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
+
+ // Let the user pick a larger buffer if they really want -- but ye
+ // gods they probably shouldn't, the minimums are horrifyingly high
+ // latency already
+ desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
+
+ mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
+ channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
+
+ audioStartThread();
+
+ //Log.v("Godot", "Godot audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + ((float)mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");
+
+ buf = new short[desiredFrames * 2];
+ return buf;
+ }
+
+ public void audioStartThread() {
+ mAudioThread = new Thread(new Runnable() {
+ public void run() {
+ mAudioTrack.play();
+ GodotLib.audio();
+ }
+ });
+
+ // I'd take REALTIME if I could get it!
+ mAudioThread.setPriority(Thread.MAX_PRIORITY);
+ mAudioThread.start();
+ }
+
+ public void audioWriteShortBuffer(short[] buffer) {
+ for (int i = 0; i < buffer.length; ) {
+ int result = mAudioTrack.write(buffer, i, buffer.length - i);
+ if (result > 0) {
+ i += result;
+ } else if (result == 0) {
+ try {
+ Thread.sleep(1);
+ } catch(InterruptedException e) {
+ // Nom nom
+ }
+ } else {
+ Log.w("Godot", "Godot audio: error return from write(short)");
+ return;
+ }
+ }
+ }
+
+
+
+ public void audioQuit() {
+ if (mAudioThread != null) {
+ try {
+ mAudioThread.join();
+ } catch(Exception e) {
+ Log.v("Godot", "Problem stopping audio thread: " + e);
+ }
+ mAudioThread = null;
+
+ //Log.v("Godot", "Finished waiting for audio thread");
+ }
+
+ if (mAudioTrack != null) {
+ mAudioTrack.stop();
+ mAudioTrack = null;
+ }
+ }
+
+ public void audioPause(boolean p_pause) {
+
+ if (p_pause)
+ mAudioTrack.pause();
+ else
+ mAudioTrack.play();
+ }
+
+ /////////////////////////
+ // MISCELANEOUS OS IO
+ /////////////////////////
+
+
+
+ public int openURI(String p_uri) {
+
+ try {
+ Log.v("MyApp", "TRYING TO OPEN URI: " + p_uri);
+ String path = p_uri;
+ String type="";
+ if (path.startsWith("/")) {
+ //absolute path to filesystem, prepend file://
+ path="file://"+path;
+ if (p_uri.endsWith(".png") || p_uri.endsWith(".jpg") || p_uri.endsWith(".gif") || p_uri.endsWith(".webp")) {
+
+ type="image/*";
+ }
+ }
+
+ Intent intent = new Intent();
+ intent.setAction(Intent.ACTION_VIEW);
+ if (!type.equals("")) {
+ intent.setDataAndType(Uri.parse(path), type);
+ } else {
+ intent.setData(Uri.parse(path));
+ }
+
+ activity.startActivity(intent);
+ return 0;
+ } catch (ActivityNotFoundException e) {
+
+ return 1;
+ }
+ }
+
+ public String getDataDir() {
+
+ return activity.getFilesDir().getAbsolutePath();
+ }
+
+ public String getLocale() {
+
+ return Locale.getDefault().toString();
+ }
+
+ public String getModel() {
+ return Build.MODEL;
+ }
+
+ public boolean needsReloadHooks() {
+
+ return android.os.Build.VERSION.SDK_INT < 11;
+ }
+
+ public void showKeyboard(String p_existing_text) {
+ if(edit != null)
+ edit.showKeyboard(p_existing_text);
+
+ //InputMethodManager inputMgr = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ //inputMgr.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
+ };
+
+ public void hideKeyboard() {
+ if(edit != null)
+ edit.hideKeyboard();
+
+ InputMethodManager inputMgr = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ View v = activity.getCurrentFocus();
+ if (v != null) {
+ inputMgr.hideSoftInputFromWindow(v.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
+ } else {
+ inputMgr.hideSoftInputFromWindow(new View(activity).getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
+ }
+ };
+
+ public void setScreenOrientation(int p_orientation) {
+
+ switch(p_orientation) {
+
+ case SCREEN_LANDSCAPE: {
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ } break;
+ case SCREEN_PORTRAIT: {
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ } break;
+ case SCREEN_REVERSE_LANDSCAPE: {
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE);
+ } break;
+ case SCREEN_REVERSE_PORTRAIT: {
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT);
+ } break;
+ case SCREEN_SENSOR_LANDSCAPE: {
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
+ } break;
+ case SCREEN_SENSOR_PORTRAIT: {
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT);
+ } break;
+ case SCREEN_SENSOR: {
+ activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
+ } break;
+
+ }
+ };
+
+ public void setEdit(GodotEditText _edit) {
+ edit = _edit;
+ }
+
+ public void playVideo(String p_path)
+ {
+ Uri filePath = Uri.parse(p_path);
+ mediaPlayer = new MediaPlayer();
+
+ try {
+ mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+ mediaPlayer.setDataSource(applicationContext, filePath);
+ mediaPlayer.prepare();
+ mediaPlayer.start();
+ }
+ catch(IOException e)
+ {
+ System.out.println("IOError while playing video");
+ }
+ }
+
+ public boolean isVideoPlaying() {
+ if (mediaPlayer != null) {
+ return mediaPlayer.isPlaying();
+ }
+ return false;
+ }
+
+ public void pauseVideo() {
+ if (mediaPlayer != null) {
+ mediaPlayer.pause();
+ }
+ }
+
+ public void stopVideo() {
+ if (mediaPlayer != null) {
+ mediaPlayer.release();
+ mediaPlayer = null;
+ }
+ }
+
+
+ public static final int SYSTEM_DIR_DESKTOP=0;
+ public static final int SYSTEM_DIR_DCIM=1;
+ public static final int SYSTEM_DIR_DOCUMENTS=2;
+ public static final int SYSTEM_DIR_DOWNLOADS=3;
+ public static final int SYSTEM_DIR_MOVIES=4;
+ public static final int SYSTEM_DIR_MUSIC=5;
+ public static final int SYSTEM_DIR_PICTURES=6;
+ public static final int SYSTEM_DIR_RINGTONES=7;
+
+
+ public String getSystemDir(int idx) {
+
+ String what="";
+ switch(idx) {
+ case SYSTEM_DIR_DESKTOP: {
+ //what=Environment.DIRECTORY_DOCUMENTS;
+ what=Environment.DIRECTORY_DOWNLOADS;
+ } break;
+ case SYSTEM_DIR_DCIM: {
+ what=Environment.DIRECTORY_DCIM;
+
+ } break;
+ case SYSTEM_DIR_DOCUMENTS: {
+ what=Environment.DIRECTORY_DOWNLOADS;
+ //what=Environment.DIRECTORY_DOCUMENTS;
+ } break;
+ case SYSTEM_DIR_DOWNLOADS: {
+ what=Environment.DIRECTORY_DOWNLOADS;
+
+ } break;
+ case SYSTEM_DIR_MOVIES: {
+ what=Environment.DIRECTORY_MOVIES;
+
+ } break;
+ case SYSTEM_DIR_MUSIC: {
+ what=Environment.DIRECTORY_MUSIC;
+ } break;
+ case SYSTEM_DIR_PICTURES: {
+ what=Environment.DIRECTORY_PICTURES;
+ } break;
+ case SYSTEM_DIR_RINGTONES: {
+ what=Environment.DIRECTORY_RINGTONES;
+
+ } break;
+ }
+
+ if (what.equals(""))
+ return "";
+ return Environment.getExternalStoragePublicDirectory(what).getAbsolutePath();
+ }
+
+ protected static final String PREFS_FILE = "device_id.xml";
+ protected static final String PREFS_DEVICE_ID = "device_id";
+
+ public static String unique_id="";
+ public String getUniqueID() {
+
+ return unique_id;
+ }
+
+}
diff --git a/platform/android/java/src/org/godotengine/godot/GodotLib.java b/platform/android/java/src/org/godotengine/godot/GodotLib.java
new file mode 100644
index 0000000000..ddbcf2e5c6
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/GodotLib.java
@@ -0,0 +1,66 @@
+/*************************************************************************/
+/* GodotLib.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2015 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+package org.godotengine.godot;
+
+// Wrapper for native library
+
+public class GodotLib {
+
+
+ public static GodotIO io;
+
+ static {
+ System.loadLibrary("godot_android");
+ }
+
+ /**
+ * @param width the current view width
+ * @param height the current view height
+ */
+
+ public static native void initialize(Godot p_instance,boolean need_reload_hook,String[] p_cmdline,Object p_asset_manager);
+ public static native void resize(int width, int height,boolean reload);
+ public static native void newcontext(boolean p_32_bits);
+ public static native void quit();
+ public static native void step();
+ public static native void touch(int what,int pointer,int howmany, int[] arr);
+ public static native void accelerometer(float x, float y, float z);
+ public static native void key(int p_scancode, int p_unicode_char, boolean p_pressed);
+ public static native void joybutton(int p_device, int p_but, boolean p_pressed);
+ public static native void joyaxis(int p_device, int p_axis, float p_value);
+ public static native void focusin();
+ public static native void focusout();
+ public static native void audio();
+ public static native void singleton(String p_name,Object p_object);
+ public static native void method(String p_sname,String p_name,String p_ret,String[] p_params);
+ public static native String getGlobal(String p_key);
+ public static native void callobject(int p_ID, String p_method, Object[] p_params);
+ public static native void calldeferred(int p_ID, String p_method, Object[] p_params);
+
+}
diff --git a/platform/android/java/src/org/godotengine/godot/GodotPaymentV3.java b/platform/android/java/src/org/godotengine/godot/GodotPaymentV3.java
new file mode 100644
index 0000000000..6bec49410d
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/GodotPaymentV3.java
@@ -0,0 +1,154 @@
+package org.godotengine.godot;
+
+import org.godotengine.godot.Dictionary;
+import android.app.Activity;
+import android.util.Log;
+
+
+public class GodotPaymentV3 extends Godot.SingletonBase {
+
+ private Godot activity;
+
+ private Integer purchaseCallbackId = 0;
+
+ private String accessToken;
+
+ private String purchaseValidationUrlPrefix;
+
+ private String transactionId;
+
+ public void purchase( String _sku, String _transactionId) {
+ final String sku = _sku;
+ final String transactionId = _transactionId;
+ activity.getPaymentsManager().setBaseSingleton(this);
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ activity.getPaymentsManager().requestPurchase(sku, transactionId);
+ }
+ });
+ }
+
+/* public string requestPurchasedTicket(){
+ activity.getPaymentsManager()
+ }
+
+*/
+ static public Godot.SingletonBase initialize(Activity p_activity) {
+
+ return new GodotPaymentV3(p_activity);
+ }
+
+
+ public GodotPaymentV3(Activity p_activity) {
+
+ registerClass("GodotPayments", new String[] {"purchase", "setPurchaseCallbackId", "setPurchaseValidationUrlPrefix", "setTransactionId", "getSignature", "consumeUnconsumedPurchases", "requestPurchased", "setAutoConsume", "consume"});
+ activity=(Godot) p_activity;
+ }
+
+ public void consumeUnconsumedPurchases(){
+ activity.getPaymentsManager().setBaseSingleton(this);
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ activity.getPaymentsManager().consumeUnconsumedPurchases();
+ }
+ });
+ }
+
+ private String signature;
+ public String getSignature(){
+ return this.signature;
+ }
+
+
+ public void callbackSuccess(String ticket, String signature, String sku){
+// Log.d(this.getClass().getName(), "PRE-Send callback to purchase success");
+ GodotLib.callobject(purchaseCallbackId, "purchase_success", new Object[]{ticket, signature, sku});
+// Log.d(this.getClass().getName(), "POST-Send callback to purchase success");
+}
+
+ public void callbackSuccessProductMassConsumed(String ticket, String signature, String sku){
+// Log.d(this.getClass().getName(), "PRE-Send callback to consume success");
+ Log.d(this.getClass().getName(), "callbackSuccessProductMassConsumed > "+ticket+","+signature+","+sku);
+ GodotLib.calldeferred(purchaseCallbackId, "consume_success", new Object[]{ticket, signature, sku});
+// Log.d(this.getClass().getName(), "POST-Send callback to consume success");
+ }
+
+ public void callbackSuccessNoUnconsumedPurchases(){
+ GodotLib.calldeferred(purchaseCallbackId, "no_validation_required", new Object[]{});
+ }
+
+ public void callbackFail(){
+ GodotLib.calldeferred(purchaseCallbackId, "purchase_fail", new Object[]{});
+// GodotLib.callobject(purchaseCallbackId, "purchase_fail", new Object[]{});
+ }
+
+ public void callbackCancel(){
+ GodotLib.calldeferred(purchaseCallbackId, "purchase_cancel", new Object[]{});
+// GodotLib.callobject(purchaseCallbackId, "purchase_cancel", new Object[]{});
+ }
+
+ public void callbackAlreadyOwned(String sku){
+ GodotLib.calldeferred(purchaseCallbackId, "purchase_owned", new Object[]{sku});
+ }
+
+ public int getPurchaseCallbackId() {
+ return purchaseCallbackId;
+ }
+
+ public void setPurchaseCallbackId(int purchaseCallbackId) {
+ this.purchaseCallbackId = purchaseCallbackId;
+ }
+
+ public String getPurchaseValidationUrlPrefix(){
+ return this.purchaseValidationUrlPrefix ;
+ }
+
+ public void setPurchaseValidationUrlPrefix(String url){
+ this.purchaseValidationUrlPrefix = url;
+ }
+
+ public String getAccessToken() {
+ return accessToken;
+ }
+
+ public void setAccessToken(String accessToken) {
+ this.accessToken = accessToken;
+ }
+
+ public void setTransactionId(String transactionId){
+ this.transactionId = transactionId;
+ }
+
+ public String getTransactionId(){
+ return this.transactionId;
+ }
+
+ // request purchased items are not consumed
+ public void requestPurchased(){
+ activity.getPaymentsManager().setBaseSingleton(this);
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ activity.getPaymentsManager().requestPurchased();
+ }
+ });
+ }
+
+ // callback for requestPurchased()
+ public void callbackPurchased(String receipt, String signature, String sku){
+ GodotLib.calldeferred(purchaseCallbackId, "has_purchased", new Object[]{receipt, signature, sku});
+ }
+
+ // consume item automatically after purchase. default is true.
+ public void setAutoConsume(boolean autoConsume){
+ activity.getPaymentsManager().setAutoConsume(autoConsume);
+ }
+
+ // consume a specific item
+ public void consume(String sku){
+ activity.getPaymentsManager().consume(sku);
+ }
+}
+
diff --git a/platform/android/java/src/org/godotengine/godot/GodotView.java b/platform/android/java/src/org/godotengine/godot/GodotView.java
new file mode 100644
index 0000000000..04b5dfa5dd
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/GodotView.java
@@ -0,0 +1,661 @@
+/*************************************************************************/
+/* GodotView.java */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* http://www.godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2015 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+package org.godotengine.godot;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.opengl.GLSurfaceView;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.content.ContextWrapper;
+import android.view.InputDevice;
+
+import java.io.File;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLConfig;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import javax.microedition.khronos.opengles.GL10;
+
+/**
+ * A simple GLSurfaceView sub-class that demonstrate how to perform
+ * OpenGL ES 2.0 rendering into a GL Surface. Note the following important
+ * details:
+ *
+ * - The class must use a custom context factory to enable 2.0 rendering.
+ * See ContextFactory class definition below.
+ *
+ * - The class must use a custom EGLConfigChooser to be able to select
+ * an EGLConfig that supports 2.0. This is done by providing a config
+ * specification to eglChooseConfig() that has the attribute
+ * EGL10.ELG_RENDERABLE_TYPE containing the EGL_OPENGL_ES2_BIT flag
+ * set. See ConfigChooser class definition below.
+ *
+ * - The class must select the surface's format, then choose an EGLConfig
+ * that matches it exactly (with regards to red/green/blue/alpha channels
+ * bit depths). Failure to do so would result in an EGL_BAD_MATCH error.
+ */
+public class GodotView extends GLSurfaceView {
+
+ private static String TAG = "GodotView";
+ private static final boolean DEBUG = false;
+ private static Context ctx;
+
+ private static GodotIO io;
+ private static boolean firsttime=true;
+ private static boolean use_gl2=false;
+ private static boolean use_32=false;
+
+ private Godot activity;
+
+ public GodotView(Context context,GodotIO p_io,boolean p_use_gl2, boolean p_use_32_bits, Godot p_activity) {
+ super(context);
+ ctx=context;
+ io=p_io;
+ use_gl2=p_use_gl2;
+ use_32=p_use_32_bits;
+
+ activity = p_activity;
+
+ if (!p_io.needsReloadHooks()) {
+ //will only work on SDK 11+!!
+ setPreserveEGLContextOnPause(true);
+ }
+
+ init(false, 16, 0);
+ }
+
+ public GodotView(Context context, boolean translucent, int depth, int stencil) {
+ super(context);
+ init(translucent, depth, stencil);
+ }
+
+ @Override public boolean onTouchEvent (MotionEvent event) {
+
+ return activity.gotTouchEvent(event);
+ };
+
+ public int get_godot_button(int keyCode) {
+
+ int button = 0;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_BUTTON_A: // Android A is SNES B
+ button = 0;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_B:
+ button = 1;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_X: // Android X is SNES Y
+ button = 2;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_Y:
+ button = 3;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_L1:
+ button = 4;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_L2:
+ button = 6;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_R1:
+ button = 5;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_R2:
+ button = 7;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_SELECT:
+ button = 10;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_START:
+ button = 11;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_THUMBL:
+ button = 8;
+ break;
+ case KeyEvent.KEYCODE_BUTTON_THUMBR:
+ button = 9;
+ break;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ button = 12;
+ break;
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ button = 13;
+ break;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ button = 14;
+ break;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ button = 15;
+ break;
+
+ default:
+ button = keyCode - KeyEvent.KEYCODE_BUTTON_1;
+ break;
+ };
+
+ return button;
+ };
+
+ @Override public boolean onKeyUp(int keyCode, KeyEvent event) {
+
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ return true;
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
+ return super.onKeyUp(keyCode, event);
+ };
+
+ int source = event.getSource();
+ if ((source & InputDevice.SOURCE_JOYSTICK) != 0 || (source & InputDevice.SOURCE_DPAD) != 0 || (source & InputDevice.SOURCE_GAMEPAD) != 0) {
+
+ int button = get_godot_button(keyCode);
+ int device = event.getDeviceId();
+
+ GodotLib.joybutton(device, button, false);
+ return true;
+ } else {
+
+ GodotLib.key(keyCode, event.getUnicodeChar(0), false);
+ };
+ return super.onKeyUp(keyCode, event);
+ };
+
+ @Override public boolean onKeyDown(int keyCode, KeyEvent event) {
+
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ GodotLib.quit();
+ // press 'back' button should not terminate program
+ // normal handle 'back' event in game logic
+ return true;
+ }
+
+ if (keyCode == KeyEvent.KEYCODE_VOLUME_UP || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
+ return super.onKeyDown(keyCode, event);
+ };
+
+ int source = event.getSource();
+ //Log.e(TAG, String.format("Key down! source %d, device %d, joystick %d, %d, %d", event.getDeviceId(), source, (source & InputDevice.SOURCE_JOYSTICK), (source & InputDevice.SOURCE_DPAD), (source & InputDevice.SOURCE_GAMEPAD)));
+
+ if ((source & InputDevice.SOURCE_JOYSTICK) != 0 || (source & InputDevice.SOURCE_DPAD) != 0 || (source & InputDevice.SOURCE_GAMEPAD) != 0) {
+
+ if (event.getRepeatCount() > 0) // ignore key echo
+ return true;
+ int button = get_godot_button(keyCode);
+ int device = event.getDeviceId();
+ //Log.e(TAG, String.format("joy button down! button %x, %d, device %d", keyCode, button, device));
+
+ GodotLib.joybutton(device, button, true);
+ return true;
+
+ } else {
+ GodotLib.key(keyCode, event.getUnicodeChar(0), true);
+ };
+ return super.onKeyDown(keyCode, event);
+ }
+
+ public float axis_value(MotionEvent p_event, InputDevice p_device, int p_axis, int p_pos) {
+
+ final InputDevice.MotionRange range = p_device.getMotionRange(p_axis, p_event.getSource());
+ if (range == null)
+ return 0;
+
+ //Log.e(TAG, String.format("axis ranges %f, %f, %f", range.getRange(), range.getMin(), range.getMax()));
+
+ final float flat = range.getFlat();
+ final float value =
+ p_pos < 0 ? p_event.getAxisValue(p_axis):
+ p_event.getHistoricalAxisValue(p_axis, p_pos);
+
+ final float absval = Math.abs(value);
+ if (absval <= flat) {
+ return 0;
+ };
+
+ final float ret = (value - range.getMin()) / range.getRange() * 2 - 1.0f;
+
+ return ret;
+ };
+
+ float[] last_axis_values = { 0, 0, 0, 0, -1, -1 };
+ boolean[] last_axis_buttons = { false, false, false, false, false, false }; // dpad up down left right, ltrigger, rtrigger
+
+ public void process_axis_state(MotionEvent p_event, int p_pos) {
+
+ int device_id = p_event.getDeviceId();
+ InputDevice device = p_event.getDevice();
+ float val;
+
+ val = axis_value(p_event, device, MotionEvent.AXIS_X, p_pos);
+ if (val != last_axis_values[0]) {
+ last_axis_values[0] = val;
+ //Log.e(TAG, String.format("axis moved! axis %d, value %f", 0, val));
+ GodotLib.joyaxis(device_id, 0, val);
+ };
+
+ val = axis_value(p_event, device, MotionEvent.AXIS_Y, p_pos);
+ if (val != last_axis_values[1]) {
+ last_axis_values[1] = val;
+ //Log.e(TAG, String.format("axis moved! axis %d, value %f", 1, val));
+ GodotLib.joyaxis(device_id, 1, val);
+ };
+
+ val = axis_value(p_event, device, MotionEvent.AXIS_Z, p_pos);
+ if (val != last_axis_values[2]) {
+ last_axis_values[2] = val;
+ //Log.e(TAG, String.format("axis moved! axis %d, value %f", 2, val));
+ GodotLib.joyaxis(device_id, 2, val);
+ };
+
+ val = axis_value(p_event, device, MotionEvent.AXIS_RZ, p_pos);
+ if (val != last_axis_values[3]) {
+ last_axis_values[3] = val;
+ //Log.e(TAG, String.format("axis moved! axis %d, value %f", 3, val));
+ GodotLib.joyaxis(device_id, 3, val);
+ };
+
+ val = axis_value(p_event, device, MotionEvent.AXIS_LTRIGGER, p_pos);
+ if (val != last_axis_values[4]) {
+ last_axis_values[4] = val;
+ if ((val != 0) != (last_axis_buttons[4])) {
+ last_axis_buttons[4] = (val != 0);
+ GodotLib.joybutton(device_id, 6, (val != 0));
+ };
+ };
+
+ val = axis_value(p_event, device, MotionEvent.AXIS_RTRIGGER, p_pos);
+ if (val != last_axis_values[5]) {
+ last_axis_values[5] = val;
+ if ((val != 0) != (last_axis_buttons[5])) {
+ last_axis_buttons[5] = (val != 0);
+ GodotLib.joybutton(device_id, 7, (val != 0));
+ };
+ };
+
+ val = axis_value(p_event, device, MotionEvent.AXIS_HAT_Y, p_pos);
+
+ if (last_axis_buttons[0] != (val > 0)) {
+ last_axis_buttons[0] = val > 0;
+ GodotLib.joybutton(device_id, 12, val > 0);
+ };
+ if (last_axis_buttons[1] != (val < 0)) {
+ last_axis_buttons[1] = val < 0;
+ GodotLib.joybutton(device_id, 13, val > 0);
+ };
+
+ val = axis_value(p_event, device, MotionEvent.AXIS_HAT_X, p_pos);
+ if (last_axis_buttons[2] != (val < 0)) {
+ last_axis_buttons[2] = val < 0;
+ GodotLib.joybutton(device_id, 14, val < 0);
+ };
+ if (last_axis_buttons[3] != (val > 0)) {
+ last_axis_buttons[3] = val > 0;
+ GodotLib.joybutton(device_id, 15, val > 0);
+ };
+ };
+
+ @Override public boolean onGenericMotionEvent(MotionEvent event) {
+
+ if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK && event.getAction() == MotionEvent.ACTION_MOVE) {
+
+ // Process all historical movement samples in the batch
+ final int historySize = event.getHistorySize();
+
+ // Process the movements starting from the
+ // earliest historical position in the batch
+ for (int i = 0; i < historySize; i++) {
+ // Process the event at historical position i
+ process_axis_state(event, i);
+ }
+
+ // Process the current movement sample in the batch (position -1)
+ process_axis_state(event, -1);
+ return true;
+
+
+ };
+
+ return super.onGenericMotionEvent(event);
+ };
+
+
+ private void init(boolean translucent, int depth, int stencil) {
+
+ this.setFocusableInTouchMode(true);
+ /* By default, GLSurfaceView() creates a RGB_565 opaque surface.
+ * If we want a translucent one, we should change the surface's
+ * format here, using PixelFormat.TRANSLUCENT for GL Surfaces
+ * is interpreted as any 32-bit surface with alpha by SurfaceFlinger.
+ */
+ if (translucent) {
+ this.getHolder().setFormat(PixelFormat.TRANSLUCENT);
+ }
+
+ /* Setup the context factory for 2.0 rendering.
+ * See ContextFactory class definition below
+ */
+ setEGLContextFactory(new ContextFactory());
+
+ /* We need to choose an EGLConfig that matches the format of
+ * our surface exactly. This is going to be done in our
+ * custom config chooser. See ConfigChooser class definition
+ * below.
+ */
+
+ if (use_32) {
+ setEGLConfigChooser( translucent ?
+ new FallbackConfigChooser(8, 8, 8, 8, 24, stencil, new ConfigChooser(8, 8, 8, 8, 16, stencil)) :
+ new FallbackConfigChooser(8, 8, 8, 8, 24, stencil, new ConfigChooser(5, 6, 5, 0, 16, stencil)) );
+
+ } else {
+ setEGLConfigChooser( translucent ?
+ new ConfigChooser(8, 8, 8, 8, 16, stencil) :
+ new ConfigChooser(5, 6, 5, 0, 16, stencil) );
+ }
+
+ /* Set the renderer responsible for frame rendering */
+ setRenderer(new Renderer());
+ }
+
+ private static class ContextFactory implements GLSurfaceView.EGLContextFactory {
+ private static int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
+ public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig) {
+ if (use_gl2)
+ Log.w(TAG, "creating OpenGL ES 2.0 context :");
+ else
+ Log.w(TAG, "creating OpenGL ES 1.1 context :");
+
+ checkEglError("Before eglCreateContext", egl);
+ int[] attrib_list2 = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
+ EGLContext context = egl.eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, use_gl2?attrib_list2:null);
+ checkEglError("After eglCreateContext", egl);
+ return context;
+ }
+
+ public void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context) {
+ egl.eglDestroyContext(display, context);
+ }
+ }
+
+ private static void checkEglError(String prompt, EGL10 egl) {
+ int error;
+ while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) {
+ Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error));
+ }
+ }
+ /* Fallback if 32bit View is not supported*/
+ private static class FallbackConfigChooser extends ConfigChooser {
+ private ConfigChooser fallback;
+
+ public FallbackConfigChooser(int r, int g, int b, int a, int depth, int stencil, ConfigChooser fallback) {
+ super(r, g, b, a, depth, stencil);
+ this.fallback = fallback;
+ }
+
+ @Override
+ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
+ EGLConfig ec = super.chooseConfig(egl, display, configs);
+ if (ec == null) {
+ Log.w(TAG, "Trying ConfigChooser fallback");
+ ec = fallback.chooseConfig(egl, display, configs);
+ use_32=false;
+ }
+ return ec;
+ }
+ }
+
+ private static class ConfigChooser implements GLSurfaceView.EGLConfigChooser {
+
+ public ConfigChooser(int r, int g, int b, int a, int depth, int stencil) {
+ mRedSize = r;
+ mGreenSize = g;
+ mBlueSize = b;
+ mAlphaSize = a;
+ mDepthSize = depth;
+ mStencilSize = stencil;
+ }
+
+ /* This EGL config specification is used to specify 2.0 rendering.
+ * We use a minimum size of 4 bits for red/green/blue, but will
+ * perform actual matching in chooseConfig() below.
+ */
+ private static int EGL_OPENGL_ES2_BIT = 4;
+ private static int[] s_configAttribs2 =
+ {
+ EGL10.EGL_RED_SIZE, 4,
+ EGL10.EGL_GREEN_SIZE, 4,
+ EGL10.EGL_BLUE_SIZE, 4,
+ // EGL10.EGL_DEPTH_SIZE, 16,
+ // EGL10.EGL_STENCIL_SIZE, EGL10.EGL_DONT_CARE,
+ EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+ EGL10.EGL_NONE
+ };
+ private static int[] s_configAttribs =
+ {
+ EGL10.EGL_RED_SIZE, 4,
+ EGL10.EGL_GREEN_SIZE, 4,
+ EGL10.EGL_BLUE_SIZE, 4,
+ // EGL10.EGL_DEPTH_SIZE, 16,
+ // EGL10.EGL_STENCIL_SIZE, EGL10.EGL_DONT_CARE,
+ EGL10.EGL_NONE
+ };
+
+ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
+
+ /* Get the number of minimally matching EGL configurations
+ */
+ int[] num_config = new int[1];
+ egl.eglChooseConfig(display, use_gl2?s_configAttribs2:s_configAttribs, null, 0, num_config);
+
+ int numConfigs = num_config[0];
+
+ if (numConfigs <= 0) {
+ throw new IllegalArgumentException("No configs match configSpec");
+ }
+
+ /* Allocate then read the array of minimally matching EGL configs
+ */
+ EGLConfig[] configs = new EGLConfig[numConfigs];
+ egl.eglChooseConfig(display, use_gl2?s_configAttribs2:s_configAttribs, configs, numConfigs, num_config);
+
+ if (DEBUG) {
+ printConfigs(egl, display, configs);
+ }
+ /* Now return the "best" one
+ */
+ return chooseConfig(egl, display, configs);
+ }
+
+ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display,
+ EGLConfig[] configs) {
+ for(EGLConfig config : configs) {
+ int d = findConfigAttrib(egl, display, config,
+ EGL10.EGL_DEPTH_SIZE, 0);
+ int s = findConfigAttrib(egl, display, config,
+ EGL10.EGL_STENCIL_SIZE, 0);
+
+ // We need at least mDepthSize and mStencilSize bits
+ if (d < mDepthSize || s < mStencilSize)
+ continue;
+
+ // We want an *exact* match for red/green/blue/alpha
+ int r = findConfigAttrib(egl, display, config,
+ EGL10.EGL_RED_SIZE, 0);
+ int g = findConfigAttrib(egl, display, config,
+ EGL10.EGL_GREEN_SIZE, 0);
+ int b = findConfigAttrib(egl, display, config,
+ EGL10.EGL_BLUE_SIZE, 0);
+ int a = findConfigAttrib(egl, display, config,
+ EGL10.EGL_ALPHA_SIZE, 0);
+
+ if (r == mRedSize && g == mGreenSize && b == mBlueSize && a == mAlphaSize)
+ return config;
+ }
+ return null;
+ }
+
+ private int findConfigAttrib(EGL10 egl, EGLDisplay display,
+ EGLConfig config, int attribute, int defaultValue) {
+
+ if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) {
+ return mValue[0];
+ }
+ return defaultValue;
+ }
+
+ private void printConfigs(EGL10 egl, EGLDisplay display,
+ EGLConfig[] configs) {
+ int numConfigs = configs.length;
+ Log.w(TAG, String.format("%d configurations", numConfigs));
+ for (int i = 0; i < numConfigs; i++) {
+ Log.w(TAG, String.format("Configuration %d:\n", i));
+ printConfig(egl, display, configs[i]);
+ }
+ }
+
+ private void printConfig(EGL10 egl, EGLDisplay display,
+ EGLConfig config) {
+ int[] attributes = {
+ EGL10.EGL_BUFFER_SIZE,
+ EGL10.EGL_ALPHA_SIZE,
+ EGL10.EGL_BLUE_SIZE,
+ EGL10.EGL_GREEN_SIZE,
+ EGL10.EGL_RED_SIZE,
+ EGL10.EGL_DEPTH_SIZE,
+ EGL10.EGL_STENCIL_SIZE,
+ EGL10.EGL_CONFIG_CAVEAT,
+ EGL10.EGL_CONFIG_ID,
+ EGL10.EGL_LEVEL,
+ EGL10.EGL_MAX_PBUFFER_HEIGHT,
+ EGL10.EGL_MAX_PBUFFER_PIXELS,
+ EGL10.EGL_MAX_PBUFFER_WIDTH,
+ EGL10.EGL_NATIVE_RENDERABLE,
+ EGL10.EGL_NATIVE_VISUAL_ID,
+ EGL10.EGL_NATIVE_VISUAL_TYPE,
+ 0x3030, // EGL10.EGL_PRESERVED_RESOURCES,
+ EGL10.EGL_SAMPLES,
+ EGL10.EGL_SAMPLE_BUFFERS,
+ EGL10.EGL_SURFACE_TYPE,
+ EGL10.EGL_TRANSPARENT_TYPE,
+ EGL10.EGL_TRANSPARENT_RED_VALUE,
+ EGL10.EGL_TRANSPARENT_GREEN_VALUE,
+ EGL10.EGL_TRANSPARENT_BLUE_VALUE,
+ 0x3039, // EGL10.EGL_BIND_TO_TEXTURE_RGB,
+ 0x303A, // EGL10.EGL_BIND_TO_TEXTURE_RGBA,
+ 0x303B, // EGL10.EGL_MIN_SWAP_INTERVAL,
+ 0x303C, // EGL10.EGL_MAX_SWAP_INTERVAL,
+ EGL10.EGL_LUMINANCE_SIZE,
+ EGL10.EGL_ALPHA_MASK_SIZE,
+ EGL10.EGL_COLOR_BUFFER_TYPE,
+ EGL10.EGL_RENDERABLE_TYPE,
+ 0x3042 // EGL10.EGL_CONFORMANT
+ };
+ String[] names = {
+ "EGL_BUFFER_SIZE",
+ "EGL_ALPHA_SIZE",
+ "EGL_BLUE_SIZE",
+ "EGL_GREEN_SIZE",
+ "EGL_RED_SIZE",
+ "EGL_DEPTH_SIZE",
+ "EGL_STENCIL_SIZE",
+ "EGL_CONFIG_CAVEAT",
+ "EGL_CONFIG_ID",
+ "EGL_LEVEL",
+ "EGL_MAX_PBUFFER_HEIGHT",
+ "EGL_MAX_PBUFFER_PIXELS",
+ "EGL_MAX_PBUFFER_WIDTH",
+ "EGL_NATIVE_RENDERABLE",
+ "EGL_NATIVE_VISUAL_ID",
+ "EGL_NATIVE_VISUAL_TYPE",
+ "EGL_PRESERVED_RESOURCES",
+ "EGL_SAMPLES",
+ "EGL_SAMPLE_BUFFERS",
+ "EGL_SURFACE_TYPE",
+ "EGL_TRANSPARENT_TYPE",
+ "EGL_TRANSPARENT_RED_VALUE",
+ "EGL_TRANSPARENT_GREEN_VALUE",
+ "EGL_TRANSPARENT_BLUE_VALUE",
+ "EGL_BIND_TO_TEXTURE_RGB",
+ "EGL_BIND_TO_TEXTURE_RGBA",
+ "EGL_MIN_SWAP_INTERVAL",
+ "EGL_MAX_SWAP_INTERVAL",
+ "EGL_LUMINANCE_SIZE",
+ "EGL_ALPHA_MASK_SIZE",
+ "EGL_COLOR_BUFFER_TYPE",
+ "EGL_RENDERABLE_TYPE",
+ "EGL_CONFORMANT"
+ };
+ int[] value = new int[1];
+ for (int i = 0; i < attributes.length; i++) {
+ int attribute = attributes[i];
+ String name = names[i];
+ if ( egl.eglGetConfigAttrib(display, config, attribute, value)) {
+ Log.w(TAG, String.format(" %s: %d\n", name, value[0]));
+ } else {
+ // Log.w(TAG, String.format(" %s: failed\n", name));
+ while (egl.eglGetError() != EGL10.EGL_SUCCESS);
+ }
+ }
+ }
+
+ // Subclasses can adjust these values:
+ protected int mRedSize;
+ protected int mGreenSize;
+ protected int mBlueSize;
+ protected int mAlphaSize;
+ protected int mDepthSize;
+ protected int mStencilSize;
+ private int[] mValue = new int[1];
+ }
+
+ private static class Renderer implements GLSurfaceView.Renderer {
+
+
+ public void onDrawFrame(GL10 gl) {
+ GodotLib.step();
+ for(int i=0;i<Godot.singleton_count;i++) {
+ Godot.singletons[i].onGLDrawFrame(gl);
+ }
+ }
+
+ public void onSurfaceChanged(GL10 gl, int width, int height) {
+
+ GodotLib.resize(width, height,!firsttime);
+ firsttime=false;
+ for(int i=0;i<Godot.singleton_count;i++) {
+ Godot.singletons[i].onGLSurfaceChanged(gl, width, height);
+ }
+ }
+
+ public void onSurfaceCreated(GL10 gl, EGLConfig config) {
+ GodotLib.newcontext(use_32);
+ }
+ }
+}
diff --git a/platform/android/java/src/org/godotengine/godot/input/GodotEditText.java b/platform/android/java/src/org/godotengine/godot/input/GodotEditText.java
new file mode 100644
index 0000000000..c8ffa74ecd
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/input/GodotEditText.java
@@ -0,0 +1,133 @@
+package org.godotengine.godot.input;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.widget.EditText;
+import org.godotengine.godot.*;
+import android.os.Handler;
+import android.os.Message;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.EditorInfo;
+
+public class GodotEditText extends EditText {
+ // ===========================================================
+ // Constants
+ // ===========================================================
+ private final static int HANDLER_OPEN_IME_KEYBOARD = 2;
+ private final static int HANDLER_CLOSE_IME_KEYBOARD = 3;
+
+ // ===========================================================
+ // Fields
+ // ===========================================================
+ private GodotView mView;
+ private GodotTextInputWrapper mInputWrapper;
+ private static Handler sHandler;
+ private String mOriginText;
+
+ // ===========================================================
+ // Constructors
+ // ===========================================================
+ public GodotEditText(final Context context) {
+ super(context);
+ this.initView();
+ }
+
+ public GodotEditText(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ this.initView();
+ }
+
+ public GodotEditText(final Context context, final AttributeSet attrs, final int defStyle) {
+ super(context, attrs, defStyle);
+ this.initView();
+ }
+
+ protected void initView() {
+ this.setPadding(0, 0, 0, 0);
+ this.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
+
+ sHandler = new Handler() {
+ @Override
+ public void handleMessage(final Message msg) {
+ switch (msg.what) {
+ case HANDLER_OPEN_IME_KEYBOARD:
+ {
+ GodotEditText edit = (GodotEditText) msg.obj;
+ String text = edit.mOriginText;
+ if (edit.requestFocus())
+ {
+ edit.removeTextChangedListener(edit.mInputWrapper);
+ edit.setText("");
+ edit.append(text);
+ edit.mInputWrapper.setOriginText(text);
+ edit.addTextChangedListener(edit.mInputWrapper);
+ final InputMethodManager imm = (InputMethodManager) mView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(edit, 0);
+ }
+ }
+ break;
+
+ case HANDLER_CLOSE_IME_KEYBOARD:
+ {
+ GodotEditText edit = (GodotEditText) msg.obj;
+
+ edit.removeTextChangedListener(mInputWrapper);
+ final InputMethodManager imm = (InputMethodManager) mView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(edit.getWindowToken(), 0);
+ edit.mView.requestFocus();
+ }
+ break;
+ }
+ }
+ };
+ }
+
+ // ===========================================================
+ // Getter & Setter
+ // ===========================================================
+ public void setView(final GodotView view) {
+ this.mView = view;
+ if(mInputWrapper == null)
+ mInputWrapper = new GodotTextInputWrapper(mView, this);
+ this.setOnEditorActionListener(mInputWrapper);
+ view.requestFocus();
+ }
+
+ // ===========================================================
+ // Methods for/from SuperClass/Interfaces
+ // ===========================================================
+ @Override
+ public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
+ super.onKeyDown(keyCode, keyEvent);
+
+ /* Let GlSurfaceView get focus if back key is input. */
+ if (keyCode == KeyEvent.KEYCODE_BACK) {
+ this.mView.requestFocus();
+ }
+
+ return true;
+ }
+
+ // ===========================================================
+ // Methods
+ // ===========================================================
+ public void showKeyboard(String p_existing_text) {
+ this.mOriginText = p_existing_text;
+
+ final Message msg = new Message();
+ msg.what = HANDLER_OPEN_IME_KEYBOARD;
+ msg.obj = this;
+ sHandler.sendMessage(msg);
+ }
+
+ public void hideKeyboard() {
+ final Message msg = new Message();
+ msg.what = HANDLER_CLOSE_IME_KEYBOARD;
+ msg.obj = this;
+ sHandler.sendMessage(msg);
+ }
+
+ // ===========================================================
+ // Inner and Anonymous Classes
+ // ===========================================================
+}
diff --git a/platform/android/java/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/src/org/godotengine/godot/input/GodotTextInputWrapper.java
new file mode 100644
index 0000000000..64d8826b44
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/input/GodotTextInputWrapper.java
@@ -0,0 +1,154 @@
+package org.godotengine.godot.input;
+import android.content.Context;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+import org.godotengine.godot.*;
+
+public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListener {
+ // ===========================================================
+ // Constants
+ // ===========================================================
+ private static final String TAG = GodotTextInputWrapper.class.getSimpleName();
+
+ // ===========================================================
+ // Fields
+ // ===========================================================
+ private final GodotView mView;
+ private final GodotEditText mEdit;
+ private String mText;
+ private String mOriginText;
+
+ // ===========================================================
+ // Constructors
+ // ===========================================================
+
+ public GodotTextInputWrapper(final GodotView view, final GodotEditText edit) {
+ this.mView = view;
+ this.mEdit = edit;
+ }
+
+ // ===========================================================
+ // Getter & Setter
+ // ===========================================================
+
+ private boolean isFullScreenEdit() {
+ final TextView textField = this.mEdit;
+ final InputMethodManager imm = (InputMethodManager) textField.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ return imm.isFullscreenMode();
+ }
+
+ public void setOriginText(final String originText) {
+ this.mOriginText = originText;
+ }
+
+ // ===========================================================
+ // Methods for/from SuperClass/Interfaces
+ // ===========================================================
+
+ @Override
+ public void afterTextChanged(final Editable s) {
+ if (this.isFullScreenEdit()) {
+ return;
+ }
+
+ //if (BuildConfig.DEBUG) {
+ //Log.d(TAG, "afterTextChanged: " + s);
+ //}
+ int nModified = s.length() - this.mText.length();
+ if (nModified > 0) {
+ final String insertText = s.subSequence(this.mText.length(), s.length()).toString();
+ for(int i = 0; i < insertText.length(); i++) {
+ int ch = insertText.codePointAt(i);
+ GodotLib.key(0, ch, true);
+ GodotLib.key(0, ch, false);
+ }
+ /*
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "insertText(" + insertText + ")");
+ }
+ */
+ } else {
+ for (; nModified < 0; ++nModified) {
+ GodotLib.key(KeyEvent.KEYCODE_DEL, 0, true);
+ GodotLib.key(KeyEvent.KEYCODE_DEL, 0, false);
+ /*
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "deleteBackward");
+ }
+ */
+ }
+ }
+ this.mText = s.toString();
+ }
+
+ @Override
+ public void beforeTextChanged(final CharSequence pCharSequence, final int start, final int count, final int after) {
+ /*
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "beforeTextChanged(" + pCharSequence + ")start: " + start + ",count: " + count + ",after: " + after);
+ }
+ */
+ this.mText = pCharSequence.toString();
+ }
+
+ @Override
+ public void onTextChanged(final CharSequence pCharSequence, final int start, final int before, final int count) {
+
+ }
+
+ @Override
+ public boolean onEditorAction(final TextView pTextView, final int pActionID, final KeyEvent pKeyEvent) {
+ if (this.mEdit == pTextView && this.isFullScreenEdit()) {
+ // user press the action button, delete all old text and insert new text
+ for (int i = this.mOriginText.length(); i > 0; i--) {
+ GodotLib.key(KeyEvent.KEYCODE_DEL, 0, true);
+ GodotLib.key(KeyEvent.KEYCODE_DEL, 0, false);
+ /*
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "deleteBackward");
+ }
+ */
+ }
+ String text = pTextView.getText().toString();
+
+ /* If user input nothing, translate "\n" to engine. */
+ if (text.compareTo("") == 0) {
+ text = "\n";
+ }
+
+ if ('\n' != text.charAt(text.length() - 1)) {
+ text += '\n';
+ }
+
+ for(int i = 0; i < text.length(); i++) {
+ int ch = text.codePointAt(i);
+ GodotLib.key(0, ch, true);
+ GodotLib.key(0, ch, false);
+ }
+ /*
+ if (BuildConfig.DEBUG) {
+ Log.d(TAG, "insertText(" + insertText + ")");
+ }
+ */
+ }
+
+ if (pActionID == EditorInfo.IME_ACTION_DONE) {
+ this.mView.requestFocus();
+ }
+ return false;
+ }
+
+ // ===========================================================
+ // Methods
+ // ===========================================================
+
+ // ===========================================================
+ // Inner and Anonymous Classes
+ // ===========================================================
+}
diff --git a/platform/android/java/src/org/godotengine/godot/payments/ConsumeTask.java b/platform/android/java/src/org/godotengine/godot/payments/ConsumeTask.java
new file mode 100644
index 0000000000..61ccb97161
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/payments/ConsumeTask.java
@@ -0,0 +1,71 @@
+package org.godotengine.godot.payments;
+
+import com.android.vending.billing.IInAppBillingService;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.RemoteException;
+import android.util.Log;
+
+abstract public class ConsumeTask {
+
+ private Context context;
+
+ private IInAppBillingService mService;
+ public ConsumeTask(IInAppBillingService mService, Context context ){
+ this.context = context;
+ this.mService = mService;
+ }
+
+
+ public void consume(final String sku){
+// Log.d("XXX", "Consuming product " + sku);
+ PaymentsCache pc = new PaymentsCache(context);
+ Boolean isBlocked = pc.getConsumableFlag("block", sku);
+ String _token = pc.getConsumableValue("token", sku);
+// Log.d("XXX", "token " + _token);
+ if(!isBlocked && _token == null){
+// _token = "inapp:"+context.getPackageName()+":android.test.purchased";
+// Log.d("XXX", "Consuming product " + sku + " with token " + _token);
+ }else if(!isBlocked){
+// Log.d("XXX", "It is not blocked ¿?");
+ return;
+ }else if(_token == null){
+// Log.d("XXX", "No token available");
+ this.error("No token for sku:" + sku);
+ return;
+ }
+ final String token = _token;
+ new AsyncTask<String, String, String>(){
+
+ @Override
+ protected String doInBackground(String... params) {
+ try {
+// Log.d("XXX", "Requesting to release item.");
+ int response = mService.consumePurchase(3, context.getPackageName(), token);
+// Log.d("XXX", "release response code: " + response);
+ if(response == 0 || response == 8){
+ return null;
+ }
+ } catch (RemoteException e) {
+ return e.getMessage();
+
+ }
+ return "Some error";
+ }
+
+ protected void onPostExecute(String param){
+ if(param == null){
+ success( new PaymentsCache(context).getConsumableValue("ticket", sku) );
+ }else{
+ error(param);
+ }
+ }
+
+ }.execute();
+ }
+
+ abstract protected void success(String ticket);
+ abstract protected void error(String message);
+
+}
diff --git a/platform/android/java/src/org/godotengine/godot/payments/GenericConsumeTask.java b/platform/android/java/src/org/godotengine/godot/payments/GenericConsumeTask.java
new file mode 100644
index 0000000000..293e903284
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/payments/GenericConsumeTask.java
@@ -0,0 +1,53 @@
+package org.godotengine.godot.payments;
+
+import com.android.vending.billing.IInAppBillingService;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.RemoteException;
+import android.util.Log;
+
+abstract public class GenericConsumeTask extends AsyncTask<String, String, String>{
+
+ private Context context;
+ private IInAppBillingService mService;
+
+
+
+
+ public GenericConsumeTask(Context context, IInAppBillingService mService, String sku, String receipt, String signature, String token){
+ this.context = context;
+ this.mService = mService;
+ this.sku = sku;
+ this.receipt = receipt;
+ this.signature = signature;
+ this.token = token;
+ }
+
+ private String sku;
+ private String receipt;
+ private String signature;
+ private String token;
+
+ @Override
+ protected String doInBackground(String... params) {
+ try {
+// Log.d("godot", "Requesting to consume an item with token ." + token);
+ int response = mService.consumePurchase(3, context.getPackageName(), token);
+// Log.d("godot", "consumePurchase response: " + response);
+ if(response == 0 || response == 8){
+ return null;
+ }
+ } catch (Exception e) {
+ Log.d("godot", "Error " + e.getClass().getName() + ":" + e.getMessage());
+ }
+ return null;
+ }
+
+ protected void onPostExecute(String sarasa){
+ onSuccess(sku, receipt, signature, token);
+ }
+
+ abstract public void onSuccess(String sku, String receipt, String signature, String token);
+
+}
diff --git a/platform/android/java/src/org/godotengine/godot/payments/HandlePurchaseTask.java b/platform/android/java/src/org/godotengine/godot/payments/HandlePurchaseTask.java
new file mode 100644
index 0000000000..d120551e4a
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/payments/HandlePurchaseTask.java
@@ -0,0 +1,82 @@
+package org.godotengine.godot.payments;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import org.godotengine.godot.GodotLib;
+import org.godotengine.godot.utils.Crypt;
+import com.android.vending.billing.IInAppBillingService;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+abstract public class HandlePurchaseTask {
+
+ private Activity context;
+
+ public HandlePurchaseTask(Activity context ){
+ this.context = context;
+ }
+
+
+ public void handlePurchaseRequest(int resultCode, Intent data){
+// Log.d("XXX", "Handling purchase response");
+// int responseCode = data.getIntExtra("RESPONSE_CODE", 0);
+ PaymentsCache pc = new PaymentsCache(context);
+
+ String purchaseData = data.getStringExtra("INAPP_PURCHASE_DATA");
+// Log.d("XXX", "Purchase data:" + purchaseData);
+ String dataSignature = data.getStringExtra("INAPP_DATA_SIGNATURE");
+ //Log.d("XXX", "Purchase signature:" + dataSignature);
+
+ if (resultCode == Activity.RESULT_OK) {
+
+ try {
+// Log.d("SARLANGA", purchaseData);
+
+
+ JSONObject jo = new JSONObject(purchaseData);
+// String sku = jo.getString("productId");
+// alert("You have bought the " + sku + ". Excellent choice, aventurer!");
+// String orderId = jo.getString("orderId");
+// String packageName = jo.getString("packageName");
+ String productId = jo.getString("productId");
+// Long purchaseTime = jo.getLong("purchaseTime");
+// Integer state = jo.getInt("purchaseState");
+ String developerPayload = jo.getString("developerPayload");
+ String purchaseToken = jo.getString("purchaseToken");
+
+ if(! pc.getConsumableValue("validation_hash", productId).equals(developerPayload) ) {
+ error("Untrusted callback");
+ return;
+ }
+// Log.d("XXX", "Este es el product ID:" + productId);
+ pc.setConsumableValue("ticket_signautre", productId, dataSignature);
+ pc.setConsumableValue("ticket", productId, purchaseData);
+ pc.setConsumableFlag("block", productId, true);
+ pc.setConsumableValue("token", productId, purchaseToken);
+
+ success(productId, dataSignature, purchaseData);
+ return;
+ } catch (JSONException e) {
+ error(e.getMessage());
+ }
+ }else if( resultCode == Activity.RESULT_CANCELED){
+ canceled();
+ }
+ }
+
+ abstract protected void success(String sku, String signature, String ticket);
+ abstract protected void error(String message);
+ abstract protected void canceled();
+
+
+}
diff --git a/platform/android/java/src/org/godotengine/godot/payments/PaymentsCache.java b/platform/android/java/src/org/godotengine/godot/payments/PaymentsCache.java
new file mode 100644
index 0000000000..5f3d931593
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/payments/PaymentsCache.java
@@ -0,0 +1,45 @@
+package org.godotengine.godot.payments;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+public class PaymentsCache {
+
+ public Context context;
+
+ public PaymentsCache(Context context){
+ this.context = context;
+ }
+
+
+ public void setConsumableFlag(String set, String sku, Boolean flag){
+ SharedPreferences sharedPref = context.getSharedPreferences("consumables_" + set, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putBoolean(sku, flag);
+ editor.commit();
+}
+
+ public boolean getConsumableFlag(String set, String sku){
+ SharedPreferences sharedPref = context.getSharedPreferences(
+ "consumables_" + set, Context.MODE_PRIVATE);
+ return sharedPref.getBoolean(sku, false);
+ }
+
+
+ public void setConsumableValue(String set, String sku, String value){
+ SharedPreferences sharedPref = context.getSharedPreferences("consumables_" + set, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString(sku, value);
+// Log.d("XXX", "Setting asset: consumables_" + set + ":" + sku);
+ editor.commit();
+ }
+
+ public String getConsumableValue(String set, String sku){
+ SharedPreferences sharedPref = context.getSharedPreferences(
+ "consumables_" + set, Context.MODE_PRIVATE);
+// Log.d("XXX", "Getting asset: consumables_" + set + ":" + sku);
+ return sharedPref.getString(sku, null);
+ }
+
+}
diff --git a/platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java b/platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java
new file mode 100644
index 0000000000..effb58aa35
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/payments/PaymentsManager.java
@@ -0,0 +1,282 @@
+package org.godotengine.godot.payments;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.os.RemoteException;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.util.Log;
+import android.os.Bundle;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONStringer;
+
+import org.godotengine.godot.Dictionary;
+import org.godotengine.godot.Godot;
+import org.godotengine.godot.GodotPaymentV3;
+import com.android.vending.billing.IInAppBillingService;
+
+public class PaymentsManager {
+
+ public static final int BILLING_RESPONSE_RESULT_OK = 0;
+ public static final int REQUEST_CODE_FOR_PURCHASE = 0x1001;
+ private static boolean auto_consume = true;
+
+ private Activity activity;
+ IInAppBillingService mService;
+
+ public void setActivity(Activity activity){
+ this.activity = activity;
+ }
+
+ public static PaymentsManager createManager(Activity activity){
+ PaymentsManager manager = new PaymentsManager(activity);
+ return manager;
+ }
+
+ private PaymentsManager(Activity activity){
+ this.activity = activity;
+ }
+
+ public PaymentsManager initService(){
+ Intent intent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
+ intent.setPackage("com.android.vending");
+ activity.bindService(
+ intent,
+ mServiceConn,
+ Context.BIND_AUTO_CREATE);
+ return this;
+ }
+
+ public void destroy(){
+ if (mService != null) {
+ activity.unbindService(mServiceConn);
+ }
+ }
+
+ ServiceConnection mServiceConn = new ServiceConnection() {
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mService = null;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mService = IInAppBillingService.Stub.asInterface(service);
+ }
+ };
+
+ public void requestPurchase(final String sku, String transactionId){
+ new PurchaseTask(mService, Godot.getInstance()) {
+
+ @Override
+ protected void error(String message) {
+ godotPaymentV3.callbackFail();
+
+ }
+
+ @Override
+ protected void canceled() {
+ godotPaymentV3.callbackCancel();
+ }
+
+ @Override
+ protected void alreadyOwned() {
+ godotPaymentV3.callbackAlreadyOwned(sku);
+ }
+
+ }.purchase(sku, transactionId);
+
+ }
+
+ public void consumeUnconsumedPurchases(){
+ new ReleaseAllConsumablesTask(mService, activity) {
+
+ @Override
+ protected void success(String sku, String receipt, String signature, String token) {
+ godotPaymentV3.callbackSuccessProductMassConsumed(receipt, signature, sku);
+ }
+
+ @Override
+ protected void error(String message) {
+ godotPaymentV3.callbackFail();
+
+ }
+
+ @Override
+ protected void notRequired() {
+ godotPaymentV3.callbackSuccessNoUnconsumedPurchases();
+
+ }
+ }.consumeItAll();
+ }
+
+ public void requestPurchased(){
+ try{
+ PaymentsCache pc = new PaymentsCache(Godot.getInstance());
+
+// Log.d("godot", "requestPurchased for " + activity.getPackageName());
+ Bundle bundle = mService.getPurchases(3, activity.getPackageName(), "inapp",null);
+
+/*
+ for (String key : bundle.keySet()) {
+ Object value = bundle.get(key);
+ Log.d("godot", String.format("%s %s (%s)", key, value.toString(), value.getClass().getName()));
+ }
+*/
+
+ if (bundle.getInt("RESPONSE_CODE") == 0){
+
+ final ArrayList<String> myPurchases = bundle.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
+ final ArrayList<String> mySignatures = bundle.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
+
+
+ if (myPurchases == null || myPurchases.size() == 0){
+// Log.d("godot", "No purchases!");
+ godotPaymentV3.callbackPurchased("", "", "");
+ return;
+ }
+
+// Log.d("godot", "# products are purchased:" + myPurchases.size());
+ for (int i=0;i<myPurchases.size();i++)
+ {
+
+ try{
+ String receipt = myPurchases.get(i);
+ JSONObject inappPurchaseData = new JSONObject(receipt);
+ String sku = inappPurchaseData.getString("productId");
+ String token = inappPurchaseData.getString("purchaseToken");
+ String signature = mySignatures.get(i);
+// Log.d("godot", "purchased item:" + token + "\n" + receipt);
+
+ pc.setConsumableValue("ticket_signautre", sku, signature);
+ pc.setConsumableValue("ticket", sku, receipt);
+ pc.setConsumableFlag("block", sku, true);
+ pc.setConsumableValue("token", sku, token);
+
+ godotPaymentV3.callbackPurchased(receipt, signature, sku);
+ } catch (JSONException e) {
+ }
+ }
+
+ }
+ }catch(Exception e){
+ Log.d("godot", "Error requesting purchased products:" + e.getClass().getName() + ":" + e.getMessage());
+ }
+ }
+
+ public void processPurchaseResponse(int resultCode, Intent data) {
+ new HandlePurchaseTask(activity){
+
+ @Override
+ protected void success(final String sku, final String signature, final String ticket) {
+ godotPaymentV3.callbackSuccess(ticket, signature, sku);
+
+ if (auto_consume){
+ new ConsumeTask(mService, activity) {
+
+ @Override
+ protected void success(String ticket) {
+// godotPaymentV3.callbackSuccess("");
+ }
+
+ @Override
+ protected void error(String message) {
+ godotPaymentV3.callbackFail();
+
+ }
+ }.consume(sku);
+ }
+
+// godotPaymentV3.callbackSuccess(new PaymentsCache(activity).getConsumableValue("ticket", sku),signature);
+// godotPaymentV3.callbackSuccess(ticket);
+ //validatePurchase(purchaseToken, sku);
+ }
+
+ @Override
+ protected void error(String message) {
+ godotPaymentV3.callbackFail();
+
+ }
+
+ @Override
+ protected void canceled() {
+ godotPaymentV3.callbackCancel();
+
+ }
+ }.handlePurchaseRequest(resultCode, data);
+ }
+
+ public void validatePurchase(String purchaseToken, final String sku){
+
+ new ValidateTask(activity, godotPaymentV3){
+
+ @Override
+ protected void success() {
+
+ new ConsumeTask(mService, activity) {
+
+ @Override
+ protected void success(String ticket) {
+ godotPaymentV3.callbackSuccess(ticket, null, sku);
+
+ }
+
+ @Override
+ protected void error(String message) {
+ godotPaymentV3.callbackFail();
+
+ }
+ }.consume(sku);
+
+ }
+
+ @Override
+ protected void error(String message) {
+ godotPaymentV3.callbackFail();
+
+ }
+
+ @Override
+ protected void canceled() {
+ godotPaymentV3.callbackCancel();
+
+ }
+ }.validatePurchase(sku);
+ }
+
+ public void setAutoConsume(boolean autoConsume){
+ auto_consume = autoConsume;
+ }
+
+ public void consume(final String sku){
+ new ConsumeTask(mService, activity) {
+
+ @Override
+ protected void success(String ticket) {
+ godotPaymentV3.callbackSuccessProductMassConsumed(ticket, "", sku);
+
+ }
+
+ @Override
+ protected void error(String message) {
+ godotPaymentV3.callbackFail();
+
+ }
+ }.consume(sku);
+ }
+
+ private GodotPaymentV3 godotPaymentV3;
+
+ public void setBaseSingleton(GodotPaymentV3 godotPaymentV3) {
+ this.godotPaymentV3 = godotPaymentV3;
+ }
+
+}
+
diff --git a/platform/android/java/src/org/godotengine/godot/payments/PurchaseTask.java b/platform/android/java/src/org/godotengine/godot/payments/PurchaseTask.java
new file mode 100644
index 0000000000..8b048d8065
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/payments/PurchaseTask.java
@@ -0,0 +1,101 @@
+package org.godotengine.godot.payments;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import org.godotengine.godot.GodotLib;
+import org.godotengine.godot.utils.Crypt;
+import com.android.vending.billing.IInAppBillingService;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+abstract public class PurchaseTask {
+
+ private Activity context;
+
+ private IInAppBillingService mService;
+ public PurchaseTask(IInAppBillingService mService, Activity context ){
+ this.context = context;
+ this.mService = mService;
+ }
+
+
+ private boolean isLooping = false;
+
+ public void purchase(final String sku, final String transactionId){
+ Log.d("XXX", "Starting purchase for: " + sku);
+ PaymentsCache pc = new PaymentsCache(context);
+ Boolean isBlocked = pc.getConsumableFlag("block", sku);
+// if(isBlocked){
+// Log.d("XXX", "Is awaiting payment confirmation");
+// error("Awaiting payment confirmation");
+// return;
+// }
+ final String hash = transactionId;
+
+ Bundle buyIntentBundle;
+ try {
+ buyIntentBundle = mService.getBuyIntent(3, context.getApplicationContext().getPackageName(), sku, "inapp", hash );
+ } catch (RemoteException e) {
+// Log.d("XXX", "Error: " + e.getMessage());
+ error(e.getMessage());
+ return;
+ }
+ Object rc = buyIntentBundle.get("RESPONSE_CODE");
+ int responseCode = 0;
+ if(rc == null){
+ responseCode = PaymentsManager.BILLING_RESPONSE_RESULT_OK;
+ }else if( rc instanceof Integer){
+ responseCode = ((Integer)rc).intValue();
+ }else if( rc instanceof Long){
+ responseCode = (int)((Long)rc).longValue();
+ }
+// Log.d("XXX", "Buy intent response code: " + responseCode);
+ if(responseCode == 1 || responseCode == 3 || responseCode == 4){
+ canceled();
+ return;
+ }
+ if(responseCode == 7){
+ alreadyOwned();
+ return;
+ }
+
+
+ PendingIntent pendingIntent = buyIntentBundle.getParcelable("BUY_INTENT");
+ pc.setConsumableValue("validation_hash", sku, hash);
+ try {
+ if(context == null){
+// Log.d("XXX", "No context!");
+ }
+ if(pendingIntent == null){
+// Log.d("XXX", "No pending intent");
+ }
+// Log.d("XXX", "Starting activity for purchase!");
+ context.startIntentSenderForResult(
+ pendingIntent.getIntentSender(),
+ PaymentsManager.REQUEST_CODE_FOR_PURCHASE,
+ new Intent(),
+ Integer.valueOf(0), Integer.valueOf(0),
+ Integer.valueOf(0));
+ } catch (SendIntentException e) {
+ error(e.getMessage());
+ }
+
+
+
+ }
+
+ abstract protected void error(String message);
+ abstract protected void canceled();
+ abstract protected void alreadyOwned();
+
+}
diff --git a/platform/android/java/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java b/platform/android/java/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java
new file mode 100644
index 0000000000..7bb5131b49
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/payments/ReleaseAllConsumablesTask.java
@@ -0,0 +1,87 @@
+package org.godotengine.godot.payments;
+
+import java.util.ArrayList;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import org.godotengine.godot.Dictionary;
+import org.godotengine.godot.Godot;
+import com.android.vending.billing.IInAppBillingService;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+abstract public class ReleaseAllConsumablesTask {
+
+ private Context context;
+ private IInAppBillingService mService;
+
+ public ReleaseAllConsumablesTask(IInAppBillingService mService, Context context ){
+ this.context = context;
+ this.mService = mService;
+ }
+
+
+ public void consumeItAll(){
+ try{
+// Log.d("godot", "consumeItall for " + context.getPackageName());
+ Bundle bundle = mService.getPurchases(3, context.getPackageName(), "inapp",null);
+
+ for (String key : bundle.keySet()) {
+ Object value = bundle.get(key);
+// Log.d("godot", String.format("%s %s (%s)", key,
+// value.toString(), value.getClass().getName()));
+ }
+
+
+ if (bundle.getInt("RESPONSE_CODE") == 0){
+
+ final ArrayList<String> myPurchases = bundle.getStringArrayList("INAPP_PURCHASE_DATA_LIST");
+ final ArrayList<String> mySignatures = bundle.getStringArrayList("INAPP_DATA_SIGNATURE_LIST");
+
+
+ if (myPurchases == null || myPurchases.size() == 0){
+// Log.d("godot", "No purchases!");
+ notRequired();
+ return;
+ }
+
+
+// Log.d("godot", "# products to be consumed:" + myPurchases.size());
+ for (int i=0;i<myPurchases.size();i++)
+ {
+
+ try{
+ String receipt = myPurchases.get(i);
+ JSONObject inappPurchaseData = new JSONObject(receipt);
+ String sku = inappPurchaseData.getString("productId");
+ String token = inappPurchaseData.getString("purchaseToken");
+ String signature = mySignatures.get(i);
+// Log.d("godot", "A punto de consumir un item con token:" + token + "\n" + receipt);
+ new GenericConsumeTask(context, mService, sku, receipt,signature, token) {
+
+ @Override
+ public void onSuccess(String sku, String receipt, String signature, String token) {
+ ReleaseAllConsumablesTask.this.success(sku, receipt, signature, token);
+ }
+ }.execute();
+
+ } catch (JSONException e) {
+ }
+ }
+
+ }
+ }catch(Exception e){
+ Log.d("godot", "Error releasing products:" + e.getClass().getName() + ":" + e.getMessage());
+ }
+ }
+
+ abstract protected void success(String sku, String receipt, String signature, String token);
+ abstract protected void error(String message);
+ abstract protected void notRequired();
+
+}
diff --git a/platform/android/java/src/org/godotengine/godot/payments/ValidateTask.java b/platform/android/java/src/org/godotengine/godot/payments/ValidateTask.java
new file mode 100644
index 0000000000..2fcf7483b4
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/payments/ValidateTask.java
@@ -0,0 +1,97 @@
+package org.godotengine.godot.payments;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import org.godotengine.godot.Godot;
+import org.godotengine.godot.GodotLib;
+import org.godotengine.godot.GodotPaymentV3;
+import org.godotengine.godot.utils.Crypt;
+import org.godotengine.godot.utils.HttpRequester;
+import org.godotengine.godot.utils.RequestParams;
+import com.android.vending.billing.IInAppBillingService;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.util.Log;
+
+abstract public class ValidateTask {
+
+ private Activity context;
+ private GodotPaymentV3 godotPaymentsV3;
+ public ValidateTask(Activity context, GodotPaymentV3 godotPaymentsV3){
+ this.context = context;
+ this.godotPaymentsV3 = godotPaymentsV3;
+ }
+
+ public void validatePurchase(final String sku){
+ new AsyncTask<String, String, String>(){
+
+
+ private ProgressDialog dialog;
+
+ @Override
+ protected void onPreExecute(){
+ dialog = ProgressDialog.show(context, null, "Please wait...");
+ }
+
+ @Override
+ protected String doInBackground(String... params) {
+ PaymentsCache pc = new PaymentsCache(context);
+ String url = godotPaymentsV3.getPurchaseValidationUrlPrefix();
+ RequestParams param = new RequestParams();
+ param.setUrl(url);
+ param.put("ticket", pc.getConsumableValue("ticket", sku));
+ param.put("purchaseToken", pc.getConsumableValue("token", sku));
+ param.put("sku", sku);
+// Log.d("XXX", "Haciendo request a " + url);
+// Log.d("XXX", "ticket: " + pc.getConsumableValue("ticket", sku));
+// Log.d("XXX", "purchaseToken: " + pc.getConsumableValue("token", sku));
+// Log.d("XXX", "sku: " + sku);
+ param.put("package", context.getApplicationContext().getPackageName());
+ HttpRequester requester = new HttpRequester();
+ String jsonResponse = requester.post(param);
+// Log.d("XXX", "Validation response:\n"+jsonResponse);
+ return jsonResponse;
+ }
+
+ @Override
+ protected void onPostExecute(String response){
+ if(dialog != null){
+ dialog.dismiss();
+ }
+ JSONObject j;
+ try {
+ j = new JSONObject(response);
+ if(j.getString("status").equals("OK")){
+ success();
+ return;
+ }else if(j.getString("status") != null){
+ error(j.getString("message"));
+ }else{
+ error("Connection error");
+ }
+ } catch (JSONException e) {
+ error(e.getMessage());
+ }catch (Exception e){
+ error(e.getMessage());
+ }
+
+
+ }
+
+ }.execute();
+ }
+ abstract protected void success();
+ abstract protected void error(String message);
+ abstract protected void canceled();
+
+
+}
diff --git a/platform/android/java/src/org/godotengine/godot/utils/Crypt.java b/platform/android/java/src/org/godotengine/godot/utils/Crypt.java
new file mode 100644
index 0000000000..2fb81cef8c
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/utils/Crypt.java
@@ -0,0 +1,39 @@
+package org.godotengine.godot.utils;
+
+import java.security.MessageDigest;
+import java.util.Random;
+
+public class Crypt {
+
+ public static String md5(String input){
+ try {
+ // Create MD5 Hash
+ MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
+ digest.update(input.getBytes());
+ byte messageDigest[] = digest.digest();
+
+ // Create Hex String
+ StringBuffer hexString = new StringBuffer();
+ for (int i=0; i<messageDigest.length; i++)
+ hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
+ return hexString.toString();
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return "";
+ }
+
+ public static String createRandomHash(){
+ return md5(Long.toString(createRandomLong()));
+ }
+
+ public static long createAbsRandomLong(){
+ return Math.abs(createRandomLong());
+ }
+
+ public static long createRandomLong(){
+ Random r = new Random();
+ return r.nextLong();
+ }
+}
diff --git a/platform/android/java/src/org/godotengine/godot/utils/CustomSSLSocketFactory.java b/platform/android/java/src/org/godotengine/godot/utils/CustomSSLSocketFactory.java
new file mode 100644
index 0000000000..2db88fcc9b
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/utils/CustomSSLSocketFactory.java
@@ -0,0 +1,54 @@
+package org.godotengine.godot.utils;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.security.KeyManagementException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.http.conn.ssl.SSLSocketFactory;
+
+
+/**
+ *
+ * @author Luis Linietsky <luis.linietsky@gmail.com>
+ */
+public class CustomSSLSocketFactory extends SSLSocketFactory {
+ SSLContext sslContext = SSLContext.getInstance("TLS");
+
+ public CustomSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException {
+ super(truststore);
+
+ TrustManager tm = new X509TrustManager() {
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ }
+
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ }
+
+ public X509Certificate[] getAcceptedIssuers() {
+ return null;
+ }
+ };
+
+ sslContext.init(null, new TrustManager[] { tm }, null);
+ }
+
+ @Override
+ public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException {
+ return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose);
+ }
+
+ @Override
+ public Socket createSocket() throws IOException {
+ return sslContext.getSocketFactory().createSocket();
+ }
+} \ No newline at end of file
diff --git a/platform/android/java/src/org/godotengine/godot/utils/HttpRequester.java b/platform/android/java/src/org/godotengine/godot/utils/HttpRequester.java
new file mode 100644
index 0000000000..14346702cc
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/utils/HttpRequester.java
@@ -0,0 +1,206 @@
+package org.godotengine.godot.utils;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpVersion;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.conn.scheme.PlainSocketFactory;
+import org.apache.http.conn.scheme.Scheme;
+import org.apache.http.conn.scheme.SchemeRegistry;
+import org.apache.http.conn.ssl.SSLSocketFactory;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.params.BasicHttpParams;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpParams;
+import org.apache.http.params.HttpProtocolParams;
+import org.apache.http.protocol.HTTP;
+import org.apache.http.util.EntityUtils;
+
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+/**
+ *
+ * @author Luis Linietsky <luis.linietsky@gmail.com>
+ */
+public class HttpRequester {
+
+ private Context context;
+ private static final int TTL = 600000; // 10 minutos
+ private long cttl=0;
+
+ public HttpRequester(){
+// Log.d("XXX", "Creando http request sin contexto");
+ }
+
+ public HttpRequester(Context context){
+ this.context=context;
+// Log.d("XXX", "Creando http request con contexto");
+ }
+
+ public String post(RequestParams params){
+ HttpPost httppost = new HttpPost(params.getUrl());
+ try {
+ httppost.setEntity(new UrlEncodedFormEntity(params.toPairsList()));
+ return request(httppost);
+ } catch (UnsupportedEncodingException e) {
+ return null;
+ }
+ }
+
+ public String get(RequestParams params){
+ String response = getResponseFromCache(params.getUrl());
+ if(response == null){
+// Log.d("XXX", "Cache miss!");
+ HttpGet httpget = new HttpGet(params.getUrl());
+ long timeInit = new Date().getTime();
+ response = request(httpget);
+ long delay = new Date().getTime() - timeInit;
+ Log.d("com.app11tt.android.utils.HttpRequest::get(url)", "Url: " + params.getUrl() + " downloaded in " + String.format("%.03f", delay/1000.0f) + " seconds");
+ if(response == null || response.length() == 0){
+ response = "";
+ }else{
+ saveResponseIntoCache(params.getUrl(), response);
+ }
+ }
+ Log.d("XXX", "Req: " + params.getUrl());
+ Log.d("XXX", "Resp: " + response);
+ return response;
+ }
+
+ private String request(HttpUriRequest request){
+// Log.d("XXX", "Haciendo request a: " + request.getURI() );
+ Log.d("PPP", "Haciendo request a: " + request.getURI() );
+ long init = new Date().getTime();
+ HttpClient httpclient = getNewHttpClient();
+ HttpParams httpParameters = httpclient.getParams();
+ HttpConnectionParams.setConnectionTimeout(httpParameters, 0);
+ HttpConnectionParams.setSoTimeout(httpParameters, 0);
+ HttpConnectionParams.setTcpNoDelay(httpParameters, true);
+ try {
+ HttpResponse response = httpclient.execute(request);
+ Log.d("PPP", "Fin de request (" + (new Date().getTime() - init) + ") a: " + request.getURI() );
+// Log.d("XXX1", "Status:" + response.getStatusLine().toString());
+ if(response.getStatusLine().getStatusCode() == 200){
+ String strResponse = EntityUtils.toString(response.getEntity());
+// Log.d("XXX2", strResponse);
+ return strResponse;
+ }else{
+ Log.d("XXX3", "Response status code:" + response.getStatusLine().getStatusCode() + "\n" + EntityUtils.toString(response.getEntity()));
+ return null;
+ }
+
+ } catch (ClientProtocolException e) {
+ Log.d("XXX3", e.getMessage());
+ } catch (IOException e) {
+ Log.d("XXX4", e.getMessage());
+ }
+ return null;
+ }
+
+ private HttpClient getNewHttpClient() {
+ try {
+ KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ trustStore.load(null, null);
+
+ SSLSocketFactory sf = new CustomSSLSocketFactory(trustStore);
+ sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
+
+ HttpParams params = new BasicHttpParams();
+ HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
+ HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
+
+ SchemeRegistry registry = new SchemeRegistry();
+ registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
+ registry.register(new Scheme("https", sf, 443));
+
+ ClientConnectionManager ccm = new ThreadSafeClientConnManager(params, registry);
+
+ return new DefaultHttpClient(ccm, params);
+ } catch (Exception e) {
+ return new DefaultHttpClient();
+ }
+ }
+
+ private static String convertStreamToString(InputStream is) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(is));
+ StringBuilder sb = new StringBuilder();
+ String line = null;
+ try {
+ while ((line = reader.readLine()) != null) {
+ sb.append((line + "\n"));
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return sb.toString();
+ }
+
+ public void saveResponseIntoCache(String request, String response){
+ if(context == null){
+// Log.d("XXX", "No context, cache failed!");
+ return;
+ }
+ SharedPreferences sharedPref = context.getSharedPreferences("http_get_cache", Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = sharedPref.edit();
+ editor.putString("request_" + Crypt.md5(request), response);
+ editor.putLong("request_" + Crypt.md5(request) + "_ttl", new Date().getTime() + getTtl());
+ editor.commit();
+ }
+
+
+ public String getResponseFromCache(String request){
+ if(context == null){
+ Log.d("XXX", "No context, cache miss");
+ return null;
+ }
+ SharedPreferences sharedPref = context.getSharedPreferences( "http_get_cache", Context.MODE_PRIVATE);
+ long ttl = getResponseTtl(request);
+ if(ttl == 0l || (new Date().getTime() - ttl) > 0l){
+ Log.d("XXX", "Cache invalid ttl:" + ttl + " vs now:" + new Date().getTime());
+ return null;
+ }
+ return sharedPref.getString("request_" + Crypt.md5(request), null);
+ }
+
+ public long getResponseTtl(String request){
+ SharedPreferences sharedPref = context.getSharedPreferences(
+ "http_get_cache", Context.MODE_PRIVATE);
+ return sharedPref.getLong("request_" + Crypt.md5(request) + "_ttl", 0l);
+ }
+
+ public long getTtl() {
+ return cttl > 0 ? cttl : TTL;
+ }
+
+ public void setTtl(long ttl) {
+ this.cttl = (ttl*1000) + new Date().getTime();
+ }
+
+}
diff --git a/platform/android/java/src/org/godotengine/godot/utils/RequestParams.java b/platform/android/java/src/org/godotengine/godot/utils/RequestParams.java
new file mode 100644
index 0000000000..36753e368c
--- /dev/null
+++ b/platform/android/java/src/org/godotengine/godot/utils/RequestParams.java
@@ -0,0 +1,58 @@
+package org.godotengine.godot.utils;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+
+import org.apache.http.NameValuePair;
+import org.apache.http.message.BasicNameValuePair;
+
+/**
+ *
+ * @author Luis Linietsky <luis.linietsky@gmail.com>
+ */
+public class RequestParams {
+
+ private HashMap<String,String> params;
+ private String url;
+
+ public RequestParams(){
+ params = new HashMap<String,String>();
+ }
+
+ public void put(String key, String value){
+ params.put(key, value);
+ }
+
+ public String get(String key){
+ return params.get(key);
+ }
+
+ public void remove(Object key){
+ params.remove(key);
+ }
+
+ public boolean has(String key){
+ return params.containsKey(key);
+ }
+
+ public List<NameValuePair> toPairsList(){
+ List<NameValuePair> fields = new ArrayList<NameValuePair>();
+
+ for(String key : params.keySet()){
+ fields.add(new BasicNameValuePair(key, this.get(key)));
+ }
+ return fields;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+
+}