diff options
author | Juan Linietsky <reduzio@gmail.com> | 2016-01-08 17:53:00 -0300 |
---|---|---|
committer | Juan Linietsky <reduzio@gmail.com> | 2016-01-08 17:53:00 -0300 |
commit | 40ba22631bbb7fc4c6b88d01402e132dbaceaf2b (patch) | |
tree | 4629c457d390ba7c7eef4c3d31ebb803fc8179ce /platform/android/java/src/org/godotengine | |
parent | 401622cc229317bd218f070dd07a3bd8db582f16 (diff) |
Renamed godot domain from com.android.godot (which was incorrect) to org.godotengine.godot
Diffstat (limited to 'platform/android/java/src/org/godotengine')
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; + } + + +} |