diff --git a/.gitignore b/.gitignore index 734d8757..212af7e2 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ local.properties framework/phonegap.jar framework/bin framework/assets/www/.DS_Store +.DS_Store diff --git a/VERSION b/VERSION index 03834411..c6989407 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.9.5 \ No newline at end of file +0.9.6.1 diff --git a/example/index.html b/example/index.html index a32ae9d5..5981abb4 100644 --- a/example/index.html +++ b/example/index.html @@ -5,8 +5,8 @@ PhoneGap - - + + @@ -31,8 +31,8 @@ Get a Picture Get Phone's Contacts Check Network - diff --git a/framework/AndroidManifest.xml b/framework/AndroidManifest.xml index ea2a6636..5b813bce 100644 --- a/framework/AndroidManifest.xml +++ b/framework/AndroidManifest.xml @@ -14,15 +14,16 @@ - + + @@ -36,8 +37,11 @@ + + + + - - + diff --git a/framework/assets/js/file.js b/framework/assets/js/file.js index d2f21cd3..11229b35 100755 --- a/framework/assets/js/file.js +++ b/framework/assets/js/file.js @@ -1044,7 +1044,7 @@ LocalFileSystem.prototype._castDate = function(pluginResult) { file.type = pluginResult.message.type; file.name = pluginResult.message.name; file.fullPath = pluginResult.message.fullPath; - file.lastModifedDate = new Date(pluginResult.message.lastModifiedDate); + file.lastModifiedDate = new Date(pluginResult.message.lastModifiedDate); pluginResult.message = file; } return pluginResult; diff --git a/framework/assets/js/network.js b/framework/assets/js/network.js index 2e092f23..be94402b 100755 --- a/framework/assets/js/network.js +++ b/framework/assets/js/network.js @@ -65,29 +65,49 @@ Network.prototype.isReachable = function(uri, callback, options) { */ var Connection = function() { this.type = null; - this.homeNW = null; - this.currentNW = null; + this._firstRun = true; + this._timer = null; + this.timeout = 500; var me = this; this.getInfo( - function(info) { - me.type = info.type; - me.homeNW = info.homeNW; - me.currentNW = info.currentNW; - PhoneGap.onPhoneGapConnectionReady.fire(); + function(type) { + // Need to send events if we are on or offline + if (type == "none") { + // set a timer if still offline at the end of timer send the offline event + me._timer = setTimeout(function(){ + me.type = type; + PhoneGap.fireEvent('offline'); + me._timer = null; + }, me.timeout); + } else { + // If there is a current offline event pending clear it + if (me._timer != null) { + clearTimeout(me._timer); + me._timer = null; + } + me.type = type; + PhoneGap.fireEvent('online'); + } + + // should only fire this once + if (me._firstRun) { + me._firstRun = false; + PhoneGap.onPhoneGapConnectionReady.fire(); + } }, function(e) { console.log("Error initializing Network Connection: " + e); }); }; -Connection.UNKNOWN = 0; -Connection.ETHERNET = 1; -Connection.WIFI = 2; -Connection.CELL_2G = 3; -Connection.CELL_3G = 4; -Connection.CELL_4G = 5; -Connection.NONE = 20; +Connection.UNKNOWN = "unknown"; +Connection.ETHERNET = "ethernet"; +Connection.WIFI = "wifi"; +Connection.CELL_2G = "2g"; +Connection.CELL_3G = "3g"; +Connection.CELL_4G = "4g"; +Connection.NONE = "none"; /** * Get connection info diff --git a/framework/assets/js/phonegap.js.base b/framework/assets/js/phonegap.js.base index 068b46e5..a1e8fe33 100755 --- a/framework/assets/js/phonegap.js.base +++ b/framework/assets/js/phonegap.js.base @@ -99,7 +99,7 @@ PhoneGap.Channel.prototype.subscribe = function(f, c, g) { if (f === null) { return; } var func = f; - if (typeof c === "object" && f instanceof Function) { func = PhoneGap.close(c, f); } + if (typeof c === "object" && typeof f === "function") { func = PhoneGap.close(c, f); } g = g || func.observer_guid || f.observer_guid || this.guid++; func.observer_guid = g; @@ -120,7 +120,7 @@ PhoneGap.Channel.prototype.subscribeOnce = function(f, c) { _this.unsubscribe(g); }; if (this.fired) { - if (typeof c === "object" && f instanceof Function) { f = PhoneGap.close(c, f); } + if (typeof c === "object" && typeof f === "function") { f = PhoneGap.close(c, f); } f.apply(this, this.fireArgs); } else { g = this.subscribe(m); @@ -132,7 +132,7 @@ PhoneGap.Channel.prototype.subscribeOnce = function(f, c) { * Unsubscribes the function with the given guid from the channel. */ PhoneGap.Channel.prototype.unsubscribe = function(g) { - if (g instanceof Function) { g = g.observer_guid; } + if (typeof g === "function") { g = g.observer_guid; } this.handlers[g] = null; delete this.handlers[g]; }; @@ -147,7 +147,7 @@ PhoneGap.Channel.prototype.fire = function(e) { for (item in this.handlers) { if (this.handlers.hasOwnProperty(item)) { handler = this.handlers[item]; - if (handler instanceof Function) { + if (typeof handler === "function") { rv = (handler.apply(this, arguments) === false); fail = fail || rv; } @@ -344,10 +344,13 @@ PhoneGap.Channel.join(function() { } else { var polling = prompt("usePolling", "gap_callbackServer:"); + PhoneGap.UsePolling = polling; if (polling == "true") { + PhoneGap.UsePolling = true; PhoneGap.JSCallbackPolling(); } else { + PhoneGap.UsePolling = false; PhoneGap.JSCallback(); } } @@ -507,7 +510,7 @@ PhoneGap.clone = function(obj) { return retVal; } - if (obj instanceof Function) { + if (typeof obj === "function") { return obj; } @@ -767,8 +770,8 @@ PhoneGap.JSCallback = function() { // If callback has JavaScript statement to execute if (xmlhttp.status === 200) { - // Need to url decode the response and replace %20 with a space - var msg = decodeURIComponent(xmlhttp.responseText.replace(/\+/g, '%20')); + // Need to url decode the response + var msg = decodeURIComponent(xmlhttp.responseText); setTimeout(function() { try { var t = eval(msg); @@ -802,13 +805,11 @@ PhoneGap.JSCallback = function() { console.log("JSCallback Error: Bad request. Stopping callbacks."); } - // If error, restart callback server + // If error, revert to polling else { console.log("JSCallback Error: Request failed."); - prompt("restartServer", "gap_callbackServer:"); - PhoneGap.JSCallbackPort = null; - PhoneGap.JSCallbackToken = null; - setTimeout(PhoneGap.JSCallback, 100); + PhoneGap.UsePolling = true; + PhoneGap.JSCallbackPolling(); } } }; @@ -935,7 +936,11 @@ PhoneGap.includeJavascript = function(jsfile, successCallback) { */ var PluginManager = { addService: function(serviceType, className) { - navigator.app.addService(serviceType, className); + try { + navigator.app.addService(serviceType, className); + } catch (e) { + console.log("Error adding service "+serviceType+": "+e); + } } }; diff --git a/framework/assets/www/index.html b/framework/assets/www/index.html index ff45efc3..fd6e0de2 100644 --- a/framework/assets/www/index.html +++ b/framework/assets/www/index.html @@ -1,7 +1,7 @@ - + diff --git a/framework/default.properties b/framework/default.properties index c5d5335e..aa2fcdf7 100644 --- a/framework/default.properties +++ b/framework/default.properties @@ -10,5 +10,5 @@ # Indicates whether an apk should be generated for each density. split.density=false # Project target. -target=android-8 +target=android-12 apk-configurations= diff --git a/framework/src/com/phonegap/App.java b/framework/src/com/phonegap/App.java index cb3342bc..13aaaee5 100755 --- a/framework/src/com/phonegap/App.java +++ b/framework/src/com/phonegap/App.java @@ -12,6 +12,7 @@ import org.json.JSONException; import org.json.JSONObject; import com.phonegap.api.Plugin; import com.phonegap.api.PluginResult; +import java.util.HashMap; /** * This class exposes methods in DroidGap that can be called from JavaScript. @@ -83,8 +84,11 @@ public class App extends Plugin { public void loadUrl(String url, JSONObject props) throws JSONException { System.out.println("App.loadUrl("+url+","+props+")"); int wait = 0; - + boolean usePhoneGap = true; + boolean clearPrev = false; + // If there are properties, then set them on the Activity + HashMap params = new HashMap(); if (props != null) { JSONArray keys = props.names(); for (int i=0; i 0) { - ((DroidGap)this.ctx).loadUrl(url, wait); - } - else { - ((DroidGap)this.ctx).loadUrl(url); + try { + synchronized(this) { + this.wait(wait); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } } + ((DroidGap)this.ctx).showWebPage(url, usePhoneGap, clearPrev, params); } /** diff --git a/framework/src/com/phonegap/AudioHandler.java b/framework/src/com/phonegap/AudioHandler.java index cd16b1b9..9673b076 100755 --- a/framework/src/com/phonegap/AudioHandler.java +++ b/framework/src/com/phonegap/AudioHandler.java @@ -73,12 +73,12 @@ public class AudioHandler extends Plugin { this.stopPlayingAudio(args.getString(0)); } else if (action.equals("getCurrentPositionAudio")) { - long l = this.getCurrentPositionAudio(args.getString(0)); - return new PluginResult(status, l); + float f = this.getCurrentPositionAudio(args.getString(0)); + return new PluginResult(status, f); } else if (action.equals("getDurationAudio")) { - long l = this.getDurationAudio(args.getString(0), args.getString(1)); - return new PluginResult(status, l); + float f = this.getDurationAudio(args.getString(0), args.getString(1)); + return new PluginResult(status, f); } else if (action.equals("release")) { boolean b = this.release(args.getString(0)); @@ -230,10 +230,10 @@ public class AudioHandler extends Plugin { * @param id The id of the audio player * @return position in msec */ - public long getCurrentPositionAudio(String id) { + public float getCurrentPositionAudio(String id) { AudioPlayer audio = this.players.get(id); if (audio != null) { - return(audio.getCurrentPosition()); + return(audio.getCurrentPosition()/1000.0f); } return -1; } @@ -245,7 +245,7 @@ public class AudioHandler extends Plugin { * @param file The name of the audio file. * @return The duration in msec. */ - public long getDurationAudio(String id, String file) { + public float getDurationAudio(String id, String file) { // Get audio file AudioPlayer audio = this.players.get(id); diff --git a/framework/src/com/phonegap/AudioPlayer.java b/framework/src/com/phonegap/AudioPlayer.java index f0b3da14..8f84a90e 100755 --- a/framework/src/com/phonegap/AudioPlayer.java +++ b/framework/src/com/phonegap/AudioPlayer.java @@ -15,6 +15,7 @@ import android.media.MediaPlayer.OnErrorListener; import android.media.MediaRecorder; import android.media.MediaPlayer.OnCompletionListener; import android.media.MediaPlayer.OnPreparedListener; +import android.os.Environment; /** * This class implements the audio playback and recording capabilities used by PhoneGap. @@ -53,7 +54,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On private String id; // The id of this player (used to identify Media object in JavaScript) private int state = MEDIA_NONE; // State of recording or playback private String audioFile = null; // File name to play or record to - private long duration = -1; // Duration of audio + private float duration = -1; // Duration of audio private MediaRecorder recorder = null; // Audio recording object private String tempFile = null; // Temporary recording file name @@ -70,10 +71,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On public AudioPlayer(AudioHandler handler, String id) { this.handler = handler; this.id = id; - - // YES, I know this is bad, but I can't do it the right way because Google didn't have the - // foresight to add android.os.environment.getExternalDataDirectory until Android 2.2 - this.tempFile = "/sdcard/tmprecording.mp3"; + this.tempFile = Environment.getExternalStorageDirectory().getAbsolutePath() + "/tmprecording.mp3"; } /** @@ -209,7 +207,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On this.mPlayer.prepare(); // Get duration - this.duration = this.mPlayer.getDuration(); + this.duration = getDurationInSeconds(); } } catch (Exception e) { @@ -319,7 +317,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On * -1=can't be determined * -2=not allowed */ - public long getDuration(String file) { + public float getDuration(String file) { // Can't get duration of recording if (this.recorder != null) { @@ -362,7 +360,7 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On } // Save off duration - this.duration = this.mPlayer.getDuration(); + this.duration = getDurationInSeconds(); this.prepareOnly = false; // Send status notification to JavaScript @@ -370,6 +368,15 @@ public class AudioPlayer implements OnCompletionListener, OnPreparedListener, On } + /** + * By default Android returns the length of audio in mills but we want seconds + * + * @return length of clip in seconds + */ + private float getDurationInSeconds() { + return (this.mPlayer.getDuration() / 1000.0f); + } + /** * Callback to be invoked when there has been an error during an asynchronous operation * (other errors will throw exceptions at method call time). diff --git a/framework/src/com/phonegap/CallbackServer.java b/framework/src/com/phonegap/CallbackServer.java index 859ac40a..2c2a9c84 100755 --- a/framework/src/com/phonegap/CallbackServer.java +++ b/framework/src/com/phonegap/CallbackServer.java @@ -11,11 +11,14 @@ import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; import java.net.ServerSocket; import java.net.Socket; import java.net.URLEncoder; import java.util.LinkedList; +import android.util.Log; + /** * This class provides a way for Java to run JavaScript in the web page that has loaded PhoneGap. * The CallbackServer class implements an XHR server and a polling server with a list of JavaScript @@ -42,6 +45,8 @@ import java.util.LinkedList; */ public class CallbackServer implements Runnable { + private static final String LOG_TAG = "CallbackServer"; + /** * The list of JavaScript statements to be sent to JavaScript. */ @@ -224,8 +229,9 @@ public class CallbackServer implements Runnable { //System.out.println("CallbackServer -- sending item"); response = "HTTP/1.1 200 OK\r\n\r\n"; String js = this.getJavascript(); - if (js != null) - response += URLEncoder.encode(js, "UTF-8"); + if (js != null) { + response += encode(js, "UTF-8"); + } } } else { @@ -321,4 +327,81 @@ public class CallbackServer implements Runnable { } } + /* The Following code has been modified from original implementation of URLEncoder */ + + /* start */ + + /* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + static final String digits = "0123456789ABCDEF"; + + /** + * This will encode the return value to JavaScript. We revert the encoding for + * common characters that don't require encoding to reduce the size of the string + * being passed to JavaScript. + * + * @param s to be encoded + * @param enc encoding type + * @return encoded string + */ + public static String encode(String s, String enc) throws UnsupportedEncodingException { + if (s == null || enc == null) { + throw new NullPointerException(); + } + // check for UnsupportedEncodingException + "".getBytes(enc); + + // Guess a bit bigger for encoded form + StringBuilder buf = new StringBuilder(s.length() + 16); + int start = -1; + for (int i = 0; i < s.length(); i++) { + char ch = s.charAt(i); + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') + || (ch >= '0' && ch <= '9') + || " .-*_'(),<>=?@[]{}:~\"\\/;!".indexOf(ch) > -1) { + if (start >= 0) { + convert(s.substring(start, i), buf, enc); + start = -1; + } + if (ch != ' ') { + buf.append(ch); + } else { + buf.append(' '); + } + } else { + if (start < 0) { + start = i; + } + } + } + if (start >= 0) { + convert(s.substring(start, s.length()), buf, enc); + } + return buf.toString(); + } + + private static void convert(String s, StringBuilder buf, String enc) throws UnsupportedEncodingException { + byte[] bytes = s.getBytes(enc); + for (int j = 0; j < bytes.length; j++) { + buf.append('%'); + buf.append(digits.charAt((bytes[j] & 0xf0) >> 4)); + buf.append(digits.charAt(bytes[j] & 0xf)); + } + } + + /* end */ } diff --git a/framework/src/com/phonegap/CameraLauncher.java b/framework/src/com/phonegap/CameraLauncher.java index 24e72d7c..ddeb9e16 100755 --- a/framework/src/com/phonegap/CameraLauncher.java +++ b/framework/src/com/phonegap/CameraLauncher.java @@ -166,7 +166,14 @@ public class CameraLauncher extends Plugin { if (resultCode == Activity.RESULT_OK) { try { // Read in bitmap of captured image - Bitmap bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); + Bitmap bitmap; + try { + bitmap = android.provider.MediaStore.Images.Media.getBitmap(this.ctx.getContentResolver(), imageUri); + } catch (FileNotFoundException e) { + Uri uri = intent.getData(); + android.content.ContentResolver resolver = this.ctx.getContentResolver(); + bitmap = android.graphics.BitmapFactory.decodeStream(resolver.openInputStream(uri)); + } // If sending base64 image back if (destType == DATA_URL) { diff --git a/framework/src/com/phonegap/ContactAccessor.java b/framework/src/com/phonegap/ContactAccessor.java index 1cc49644..fdbb996f 100644 --- a/framework/src/com/phonegap/ContactAccessor.java +++ b/framework/src/com/phonegap/ContactAccessor.java @@ -178,11 +178,13 @@ public abstract class ContactAccessor { protected String getJsonString(JSONObject obj, String property) { String value = null; try { + if (obj != null) { value = obj.getString(property); - if (value.equals("null")) { - Log.d(LOG_TAG, property + " is string called 'null'"); - value = null; - } + if (value.equals("null")) { + Log.d(LOG_TAG, property + " is string called 'null'"); + value = null; + } + } } catch (JSONException e) { Log.d(LOG_TAG, "Could not get = " + e.getMessage()); diff --git a/framework/src/com/phonegap/ContactAccessorSdk5.java b/framework/src/com/phonegap/ContactAccessorSdk5.java index a1e7d428..e8b34ae8 100644 --- a/framework/src/com/phonegap/ContactAccessorSdk5.java +++ b/framework/src/com/phonegap/ContactAccessorSdk5.java @@ -1420,7 +1420,6 @@ public class ContactAccessorSdk5 extends ContactAccessor { .build()); } - // Add urls JSONArray websites = null; try { @@ -1473,7 +1472,6 @@ public class ContactAccessorSdk5 extends ContactAccessor { Log.e(LOG_TAG, e.getMessage(), e); retVal = false; } - return retVal; } diff --git a/framework/src/com/phonegap/Device.java b/framework/src/com/phonegap/Device.java index ac0a8ecc..d86b2d06 100755 --- a/framework/src/com/phonegap/Device.java +++ b/framework/src/com/phonegap/Device.java @@ -14,13 +14,11 @@ import org.json.JSONObject; import com.phonegap.api.PhonegapActivity; import com.phonegap.api.Plugin; import com.phonegap.api.PluginResult; -import android.content.Context; import android.provider.Settings; -import android.telephony.TelephonyManager; public class Device extends Plugin { - public static String phonegapVersion = "0.9.5"; // PhoneGap version + public static String phonegapVersion = "0.9.6.1"; // PhoneGap version public static String platform = "Android"; // Device OS public static String uuid; // Device UUID @@ -116,34 +114,13 @@ public class Device extends Plugin { public String getPhonegapVersion() { return Device.phonegapVersion; } - - public String getLine1Number(){ - TelephonyManager operator = (TelephonyManager)this.ctx.getSystemService(Context.TELEPHONY_SERVICE); - return operator.getLine1Number(); - } - public String getDeviceId(){ - TelephonyManager operator = (TelephonyManager)this.ctx.getSystemService(Context.TELEPHONY_SERVICE); - return operator.getDeviceId(); - } - - public String getSimSerialNumber(){ - TelephonyManager operator = (TelephonyManager)this.ctx.getSystemService(Context.TELEPHONY_SERVICE); - return operator.getSimSerialNumber(); - } - - public String getSubscriberId(){ - TelephonyManager operator = (TelephonyManager)this.ctx.getSystemService(Context.TELEPHONY_SERVICE); - return operator.getSubscriberId(); - } - - public String getModel() - { + public String getModel() { String model = android.os.Build.MODEL; return model; } - public String getProductName() - { + + public String getProductName() { String productname = android.os.Build.PRODUCT; return productname; } @@ -158,8 +135,7 @@ public class Device extends Plugin { return osversion; } - public String getSDKVersion() - { + public String getSDKVersion() { String sdkversion = android.os.Build.VERSION.SDK; return sdkversion; } diff --git a/framework/src/com/phonegap/DroidGap.java b/framework/src/com/phonegap/DroidGap.java index 91eeb173..3830722e 100755 --- a/framework/src/com/phonegap/DroidGap.java +++ b/framework/src/com/phonegap/DroidGap.java @@ -7,38 +7,49 @@ */ package com.phonegap; +import java.util.HashMap; +import java.util.Map.Entry; import org.json.JSONArray; import org.json.JSONException; + +import android.app.Activity; import android.app.AlertDialog; -import android.widget.EditText; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; -import android.graphics.Bitmap; import android.graphics.Color; +import android.graphics.Rect; import android.media.AudioManager; import android.net.Uri; +import android.net.http.SslError; import android.os.Bundle; import android.util.Log; +import android.view.Display; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; -import android.webkit.JsResult; -import android.webkit.WebChromeClient; +import android.webkit.GeolocationPermissions.Callback; import android.webkit.JsPromptResult; +import android.webkit.JsResult; +import android.webkit.SslErrorHandler; +import android.webkit.WebChromeClient; import android.webkit.WebSettings; +import android.webkit.WebSettings.LayoutAlgorithm; import android.webkit.WebStorage; import android.webkit.WebView; import android.webkit.WebViewClient; -import android.webkit.GeolocationPermissions.Callback; -import android.webkit.WebSettings.LayoutAlgorithm; +import android.widget.EditText; import android.widget.LinearLayout; + +import com.phonegap.api.PhonegapActivity; import com.phonegap.api.Plugin; import com.phonegap.api.PluginManager; -import com.phonegap.api.PhonegapActivity; /** * This class is the main Android activity that represents the PhoneGap @@ -119,10 +130,15 @@ public class DroidGap extends PhonegapActivity { protected boolean clearHistory = false; // The initial URL for our app + // ie http://server/path/index.html#abc?query private String url; + // The initial URL for our app up to and including the file name + // ie http://server/path/index.html + private String urlFile; + // The base of the initial URL for our app - private String baseUrl; + private String baseUrl = null; // Plugin to call when activity result is received protected Plugin activityResultCallback = null; @@ -168,7 +184,11 @@ public class DroidGap extends PhonegapActivity { WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); // This builds the view. We could probably get away with NOT having a LinearLayout, but I like having a bucket! - root = new LinearLayout(this); + Display display = getWindowManager().getDefaultDisplay(); + int width = display.getWidth(); + int height = display.getHeight(); + + root = new LinearLayoutSoftKeyboardDetect(this, width, height); root.setOrientation(LinearLayout.VERTICAL); root.setBackgroundColor(Color.BLACK); root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, @@ -330,13 +350,16 @@ public class DroidGap extends PhonegapActivity { */ public void loadUrl(final String url) { System.out.println("loadUrl("+url+")"); + this.urlFile = this.getUrlFile(url); this.url = url; - int i = url.lastIndexOf('/'); - if (i > 0) { - this.baseUrl = url.substring(0, i); - } - else { - this.baseUrl = this.url; + if (this.baseUrl == null) { + int i = url.lastIndexOf('/'); + if (i > 0) { + this.baseUrl = url.substring(0, i+1); + } + else { + this.baseUrl = this.url + "/"; + } } System.out.println("url="+url+" baseUrl="+baseUrl); @@ -608,17 +631,28 @@ public class DroidGap extends PhonegapActivity { // Send pause event to JavaScript this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};"); + // Forward to plugins + this.pluginManager.onPause(this.keepRunning); + // If app doesn't want to run in background if (!this.keepRunning) { - - // Forward to plugins - this.pluginManager.onPause(); // Pause JavaScript timers (including setInterval) this.appView.pauseTimers(); } } + @Override + /** + * Called when the activity receives a new intent + **/ + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + + //Forward to plugins + this.pluginManager.onNewIntent(intent); + } + @Override /** * Called when the activity will start interacting with the user. @@ -632,6 +666,9 @@ public class DroidGap extends PhonegapActivity { // Send resume event to JavaScript this.appView.loadUrl("javascript:try{PhoneGap.onResume.fire();}catch(e){};"); + // Forward to plugins + this.pluginManager.onResume(this.keepRunning || this.activityResultKeepRunning); + // If app doesn't want to run in background if (!this.keepRunning || this.activityResultKeepRunning) { @@ -641,9 +678,6 @@ public class DroidGap extends PhonegapActivity { this.activityResultKeepRunning = false; } - // Forward to plugins - this.pluginManager.onResume(); - // Resume JavaScript timers (including setInterval) this.appView.resumeTimers(); } @@ -658,21 +692,18 @@ public class DroidGap extends PhonegapActivity { if (this.appView != null) { - // Make sure pause event is sent if onPause hasn't been called before onDestroy - this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};"); + // Make sure pause event is sent if onPause hasn't been called before onDestroy + this.appView.loadUrl("javascript:try{PhoneGap.onPause.fire();}catch(e){};"); - // Send destroy event to JavaScript - this.appView.loadUrl("javascript:try{PhoneGap.onDestroy.fire();}catch(e){};"); + // Send destroy event to JavaScript + this.appView.loadUrl("javascript:try{PhoneGap.onDestroy.fire();}catch(e){};"); - // Load blank page so that JavaScript onunload is called - this.appView.loadUrl("about:blank"); + // Load blank page so that JavaScript onunload is called + this.appView.loadUrl("about:blank"); - // Forward to plugins - this.pluginManager.onDestroy(); + // Forward to plugins + this.pluginManager.onDestroy(); - if (this.callbackServer != null) { - this.callbackServer.destroy(); - } } } @@ -696,13 +727,82 @@ public class DroidGap extends PhonegapActivity { this.callbackServer.sendJavascript(statement); } + /** + * Return up to file part of url. + * If url = http://server/page.html#abc, then return http://server/page.html + * + * @param url + * @return + */ + private String getUrlFile(String url) { + int p1 = url.indexOf("#"); + int p2 = url.indexOf("?"); + if (p1 < 0) p1 = url.length(); + if (p2 < 0) p2 = url.length(); + int p3 = (p1 < p2) ? p1 : p2; + return url.substring(0, p3); + } + + /** + * Display a new browser with the specified URL. + * + * NOTE: If usePhoneGap is set, only trusted PhoneGap URLs should be loaded, + * since any PhoneGap API can be called by the loaded HTML page. + * + * @param url The url to load. + * @param usePhoneGap Load url in PhoneGap webview. + * @param clearPrev Clear the activity stack, so new app becomes top of stack + * @param params DroidGap parameters for new app + * @throws android.content.ActivityNotFoundException + */ + public void showWebPage(String url, boolean usePhoneGap, boolean clearPrev, HashMap params) throws android.content.ActivityNotFoundException { + Intent intent = null; + if (usePhoneGap) { + intent = new Intent().setClass(this, com.phonegap.DroidGap.class); + intent.putExtra("url", url); + + // Add parameters + if (params != null) { + java.util.Set> s = params.entrySet(); + java.util.Iterator> it = s.iterator(); + while(it.hasNext()) { + Entry entry = it.next(); + String key = entry.getKey(); + Object value = entry.getValue(); + if (value == null) { + } + else if (value.getClass().equals(String.class)) { + intent.putExtra(key, (String)value); + } + else if (value.getClass().equals(Boolean.class)) { + intent.putExtra(key, (Boolean)value); + } + else if (value.getClass().equals(Integer.class)) { + intent.putExtra(key, (Integer)value); + } + } + + } + } + else { + intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + } + this.startActivity(intent); + + // Finish current activity + if (clearPrev) { + this.finish(); + } + } + /** * Provides a hook for calling "alert" from javascript. Useful for * debugging your javascript. */ public class GapClient extends WebChromeClient { - private Context ctx; + private DroidGap ctx; /** * Constructor. @@ -710,7 +810,7 @@ public class DroidGap extends PhonegapActivity { * @param ctx */ public GapClient(Context ctx) { - this.ctx = ctx; + this.ctx = (DroidGap)ctx; } /** @@ -782,10 +882,17 @@ public class DroidGap extends PhonegapActivity { */ @Override public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) { + + // Security check to make sure any requests are coming from the page initially + // loaded in webview and not another loaded in an iframe. + boolean reqOk = false; + if (url.indexOf(this.ctx.baseUrl) == 0) { + reqOk = true; + } // Calling PluginManager.exec() to call a native service using // prompt(this.stringify(args), "gap:"+this.stringify([service, action, callbackId, true])); - if (defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) { + if (reqOk && defaultValue != null && defaultValue.length() > 3 && defaultValue.substring(0, 4).equals("gap:")) { JSONArray array; try { array = new JSONArray(defaultValue.substring(4)); @@ -801,13 +908,13 @@ public class DroidGap extends PhonegapActivity { } // Polling for JavaScript messages - else if (defaultValue.equals("gap_poll:")) { + else if (reqOk && defaultValue.equals("gap_poll:")) { String r = callbackServer.getJavascript(); result.confirm(r); } // Calling into CallbackServer - else if (defaultValue.equals("gap_callbackServer:")) { + else if (reqOk && defaultValue.equals("gap_callbackServer:")) { String r = ""; if (message.equals("usePolling")) { r = ""+callbackServer.usePolling(); @@ -1022,17 +1129,18 @@ public class DroidGap extends PhonegapActivity { // All else else { - int i = url.lastIndexOf('/'); - String newBaseUrl = url; - if (i > 0) { - newBaseUrl = url.substring(0, i); - } - // If our app or file:, then load into our webview // NOTE: This replaces our app with new URL. When BACK is pressed, // our app is reloaded and restarted. All state is lost. - if (this.ctx.loadInWebView || url.startsWith("file://") || this.ctx.baseUrl.equals(newBaseUrl)) { - this.ctx.appView.loadUrl(url); + if (this.ctx.loadInWebView || url.startsWith("file://") || url.indexOf(this.ctx.baseUrl) == 0) { + try { + HashMap params = new HashMap(); + params.put("loadingDialog", ""); + params.put("hideLoadingDialogOnPageLoad", true); + this.ctx.showWebPage(url, true, false, params); + } catch (android.content.ActivityNotFoundException e) { + System.out.println("Error loading url into DroidGap - "+url+":"+ e.toString()); + } } // If not our application, let default viewer handle @@ -1065,7 +1173,9 @@ public class DroidGap extends PhonegapActivity { // Try firing the onNativeReady event in JS. If it fails because the JS is // not loaded yet then just set a flag so that the onNativeReady can be fired // from the JS side when the JS gets to that code. - appView.loadUrl("javascript:try{ PhoneGap.onNativeReady.fire();}catch(e){_nativeReady = true;}"); + if (!url.equals("about:blank")) { + appView.loadUrl("javascript:try{ PhoneGap.onNativeReady.fire();}catch(e){_nativeReady = true;}"); + } // Make app view visible appView.setVisibility(View.VISIBLE); @@ -1081,6 +1191,13 @@ public class DroidGap extends PhonegapActivity { this.ctx.clearHistory = false; this.ctx.appView.clearHistory(); } + + // Shutdown if blank loaded + if (url.equals("about:blank")) { + if (this.ctx.callbackServer != null) { + this.ctx.callbackServer.destroy(); + } + } } /** @@ -1105,6 +1222,27 @@ public class DroidGap extends PhonegapActivity { // Handle error this.ctx.onReceivedError(errorCode, description, failingUrl); } + + public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { + + final String packageName = this.ctx.getPackageName(); + final PackageManager pm = this.ctx.getPackageManager(); + ApplicationInfo appInfo; + try { + appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); + if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) { + // debug = true + handler.proceed(); + return; + } else { + // debug = false + super.onReceivedSslError(view, handler, error); + } + } catch (NameNotFoundException e) { + // When it doubt, lock it out! + super.onReceivedSslError(view, handler, error); + } + } } /** @@ -1125,6 +1263,7 @@ public class DroidGap extends PhonegapActivity { // If back key is bound, then send event to JavaScript if (this.bound) { this.appView.loadUrl("javascript:PhoneGap.fireEvent('backbutton');"); + return true; } // If not bound @@ -1133,6 +1272,7 @@ public class DroidGap extends PhonegapActivity { // Go to previous page in webview if it is possible to go back if (this.appView.canGoBack()) { this.appView.goBack(); + return true; } // If not, then invoke behavior of super class @@ -1145,11 +1285,13 @@ public class DroidGap extends PhonegapActivity { // If menu key else if (keyCode == KeyEvent.KEYCODE_MENU) { this.appView.loadUrl("javascript:PhoneGap.fireEvent('menubutton');"); + return true; } // If search key else if (keyCode == KeyEvent.KEYCODE_SEARCH) { this.appView.loadUrl("javascript:PhoneGap.fireEvent('searchbutton');"); + return true; } return false; @@ -1276,4 +1418,84 @@ public class DroidGap extends PhonegapActivity { } }); } + + /** + * We are providing this class to detect when the soft keyboard is shown + * and hidden in the web view. + */ + class LinearLayoutSoftKeyboardDetect extends LinearLayout { + + private static final String LOG_TAG = "SoftKeyboardDetect"; + + private int oldHeight = 0; // Need to save the old height as not to send redundant events + private int oldWidth = 0; // Need to save old width for orientation change + private int screenWidth = 0; + private int screenHeight = 0; + + public LinearLayoutSoftKeyboardDetect(Context context, int width, int height) { + super(context); + screenWidth = width; + screenHeight = height; + } + + @Override + /** + * Start listening to new measurement events. Fire events when the height + * gets smaller fire a show keyboard event and when height gets bigger fire + * a hide keyboard event. + * + * Note: We are using callbackServer.sendJavascript() instead of + * this.appView.loadUrl() as changing the URL of the app would cause the + * soft keyboard to go away. + * + * @param widthMeasureSpec + * @param heightMeasureSpec + */ + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + Log.d(LOG_TAG, "We are in our onMeasure method"); + + // Get the current height of the visible part of the screen. + // This height will not included the status bar. + int height = MeasureSpec.getSize(heightMeasureSpec); + int width = MeasureSpec.getSize(widthMeasureSpec); + + Log.d(LOG_TAG, "Old Height = " + oldHeight); + Log.d(LOG_TAG, "Height = " + height); + Log.d(LOG_TAG, "Old Width = " + oldWidth); + Log.d(LOG_TAG, "Width = " + width); + + + // If the oldHeight = 0 then this is the first measure event as the app starts up. + // If oldHeight == height then we got a measurement change that doesn't affect us. + if (oldHeight == 0 || oldHeight == height) { + Log.d(LOG_TAG, "Ignore this event"); + } + // Account for orientation change and ignore this event/Fire orientation change + else if(screenHeight == width) + { + int tmp_var = screenHeight; + screenHeight = screenWidth; + screenWidth = tmp_var; + Log.d(LOG_TAG, "Orientation Change"); + } + // If the height as gotten bigger then we will assume the soft keyboard has + // gone away. + else if (height > oldHeight) { + Log.d(LOG_TAG, "Throw hide keyboard event"); + callbackServer.sendJavascript("PhoneGap.fireEvent('hidekeyboard');"); + } + // If the height as gotten smaller then we will assume the soft keyboard has + // been displayed. + else if (height < oldHeight) { + Log.d(LOG_TAG, "Throw show keyboard event"); + callbackServer.sendJavascript("PhoneGap.fireEvent('showkeyboard');"); + } + + // Update the old height for the next event + oldHeight = height; + oldWidth = width; + } + } } diff --git a/framework/src/com/phonegap/FileUtils.java b/framework/src/com/phonegap/FileUtils.java index a2ed7a11..08ff7cce 100755 --- a/framework/src/com/phonegap/FileUtils.java +++ b/framework/src/com/phonegap/FileUtils.java @@ -10,6 +10,7 @@ package com.phonegap; import java.io.*; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLDecoder; import java.nio.channels.FileChannel; import org.apache.commons.codec.binary.Base64; @@ -229,15 +230,16 @@ public class FileUtils extends Plugin { * @throws JSONException */ private JSONObject resolveLocalFileSystemURI(String url) throws IOException, JSONException { + String decoded = URLDecoder.decode(url, "UTF-8"); // Test to see if this is a valid URL first - @SuppressWarnings("unused") - URL testUrl = new URL(url); - + @SuppressWarnings("unused") + URL testUrl = new URL(decoded); + File fp = null; - if (url.startsWith("file://")) { - fp = new File(url.substring(7, url.length())); + if (decoded.startsWith("file://")) { + fp = new File(decoded.substring(7, decoded.length())); } else { - fp = new File(url); + fp = new File(decoded); } if (!fp.exists()) { throw new FileNotFoundException(); @@ -411,7 +413,7 @@ public class FileUtils extends Plugin { } // Check to make sure we are not copying the directory into itself - if (destinationDir.getAbsolutePath().startsWith(srcDir.getAbsolutePath())) { + if (isCopyOnItself(srcDir.getAbsolutePath(), destinationDir.getAbsolutePath())) { throw new InvalidModificationException("Can't copy itself into itself"); } @@ -435,6 +437,26 @@ public class FileUtils extends Plugin { return getEntry(destinationDir); } + /** + * Check to see if the user attempted to copy an entry into its parent without changing its name, + * or attempted to copy a directory into a directory that it contains directly or indirectly. + * + * @param srcDir + * @param destinationDir + * @return + */ + private boolean isCopyOnItself(String src, String dest) { + + // This weird test is to determine if we are copying or moving a directory into itself. + // Copy /sdcard/myDir to /sdcard/myDir-backup is okay but + // Copy /sdcard/myDir to /sdcard/myDir/backup should thow an INVALID_MODIFICATION_ERR + if (dest.startsWith(src) && dest.indexOf(File.separator, src.length()-1) != -1) { + return true; + } + + return false; + } + /** * Move a file * @@ -480,8 +502,8 @@ public class FileUtils extends Plugin { } // Check to make sure we are not copying the directory into itself - if (destinationDir.getAbsolutePath().startsWith(srcDir.getAbsolutePath())) { - throw new InvalidModificationException("Can't copy itself into itself"); + if (isCopyOnItself(srcDir.getAbsolutePath(), destinationDir.getAbsolutePath())) { + throw new InvalidModificationException("Can't move itself into itself"); } // If the destination directory already exists and is empty then delete it. This is according to spec. diff --git a/framework/src/com/phonegap/NetworkManager.java b/framework/src/com/phonegap/NetworkManager.java index 779e1c71..62140f31 100755 --- a/framework/src/com/phonegap/NetworkManager.java +++ b/framework/src/com/phonegap/NetworkManager.java @@ -11,7 +11,6 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.json.JSONArray; import org.json.JSONException; -import org.json.JSONObject; import com.phonegap.api.PhonegapActivity; import com.phonegap.api.Plugin; @@ -21,13 +20,13 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.net.*; -import android.telephony.TelephonyManager; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.util.Log; public class NetworkManager extends Plugin { - public static int NOT_REACHABLE = 0; + public static int NOT_REACHABLE = 0; public static int REACHABLE_VIA_CARRIER_DATA_NETWORK = 1; public static int REACHABLE_VIA_WIFI_NETWORK = 2; @@ -46,24 +45,26 @@ public class NetworkManager extends Plugin { public static final String LTE = "lte"; public static final String UMB = "umb"; // return types - public static final int TYPE_UNKNOWN = 0; - public static final int TYPE_ETHERNET = 1; - public static final int TYPE_WIFI = 2; - public static final int TYPE_2G = 3; - public static final int TYPE_3G = 4; - public static final int TYPE_4G = 5; - public static final int TYPE_NONE = 20; + public static final String TYPE_UNKNOWN = "unknown"; + public static final String TYPE_ETHERNET = "ethernet"; + public static final String TYPE_WIFI = "wifi"; + public static final String TYPE_2G = "2g"; + public static final String TYPE_3G = "3g"; + public static final String TYPE_4G = "4g"; + public static final String TYPE_NONE = "none"; private static final String LOG_TAG = "NetworkManager"; + private String connectionCallbackId; - ConnectivityManager sockMan; - TelephonyManager telephonyManager; + ConnectivityManager sockMan; + BroadcastReceiver receiver; /** * Constructor. */ public NetworkManager() { + this.receiver = null; } /** @@ -75,19 +76,23 @@ public class NetworkManager extends Plugin { public void setContext(PhonegapActivity ctx) { super.setContext(ctx); this.sockMan = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE); - this.telephonyManager = ((TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE)); + this.connectionCallbackId = null; // We need to listen to connectivity events to update navigator.connection IntentFilter intentFilter = new IntentFilter() ; intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); - ctx.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - updateConnectionInfo((NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO)); - } - }, intentFilter); - } + if (this.receiver == null) { + this.receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + updateConnectionInfo((NetworkInfo) intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO)); + } + }; + ctx.registerReceiver(this.receiver, intentFilter); + } + } + /** * Executes the request and returns PluginResult. * @@ -135,6 +140,19 @@ public class NetworkManager extends Plugin { // All methods take a while, so always use async return false; } + + /** + * Stop network receiver. + */ + public void onDestroy() { + if (this.receiver != null) { + try { + this.ctx.unregisterReceiver(this.receiver); + } catch (Exception e) { + Log.e(LOG_TAG, "Error unregistering network receiver: " + e.getMessage(), e); + } + } + } //-------------------------------------------------------------------------- // LOCAL METHODS @@ -147,11 +165,9 @@ public class NetworkManager extends Plugin { * @param info the current active network info * @return */ - private void updateConnectionInfo(NetworkInfo info) { - JSONObject connection = this.getConnectionInfo(info); - - // send update to javascript "navigator.connection" - sendUpdate(connection); + private void updateConnectionInfo(NetworkInfo info) { + // send update to javascript "navigator.network.connection" + sendUpdate(this.getConnectionInfo(info)); } /** @@ -160,40 +176,18 @@ public class NetworkManager extends Plugin { * @param info the current active network info * @return a JSONObject that represents the network info */ - private JSONObject getConnectionInfo(NetworkInfo info) { - JSONObject connection = new JSONObject(); - - try { - if (info != null) { - // If we are not connected to any network set type to none - if (!info.isConnected()) { - connection.put("type", TYPE_NONE); - connection.put("homeNW", null); - connection.put("currentNW", null); - } - else { - // If we are connected check which type - // First off is wifi - if (info.getTypeName().toLowerCase().equals(WIFI)) { - connection.put("type", TYPE_WIFI); - connection.put("homeNW", null); - connection.put("currentNW", null); - } - // Otherwise it must be one of the mobile network protocols - else { - // Determine the correct type, 2G, 3G, 4G - connection.put("type", getType(info)); - connection.put("homeNW", telephonyManager.getSimOperatorName()); - connection.put("currentNW", telephonyManager.getNetworkOperatorName()); - } - } + private String getConnectionInfo(NetworkInfo info) { + String type = TYPE_NONE; + if (info != null) { + // If we are not connected to any network set type to none + if (!info.isConnected()) { + type = TYPE_NONE; + } + else { + type = getType(info); } } - catch (JSONException e) { - // this should never happen - Log.e(LOG_TAG, e.getMessage(), e); - } - return connection; + return type; } /** @@ -201,8 +195,8 @@ public class NetworkManager extends Plugin { * * @param connection the network info to set as navigator.connection */ - private void sendUpdate(JSONObject connection) { - PluginResult result = new PluginResult(PluginResult.Status.OK, connection); + private void sendUpdate(String type) { + PluginResult result = new PluginResult(PluginResult.Status.OK, type); result.setKeepCallback(true); this.success(result, this.connectionCallbackId); } @@ -213,11 +207,14 @@ public class NetworkManager extends Plugin { * @param info the network info so we can determine connection type. * @return the type of mobile network we are on */ - private int getType(NetworkInfo info) { + private String getType(NetworkInfo info) { if (info != null) { String type = info.getTypeName(); - if (type.toLowerCase().equals(MOBILE)) { + if (type.toLowerCase().equals(WIFI)) { + return TYPE_WIFI; + } + else if (type.toLowerCase().equals(MOBILE)) { type = info.getSubtypeName(); if (type.toLowerCase().equals(GSM) || type.toLowerCase().equals(GPRS) || @@ -278,7 +275,7 @@ public class NetworkManager extends Plugin { public int isReachable(String uri, boolean isIpAddress) { int reachable = NOT_REACHABLE; - if (uri.indexOf("http://") == -1) { + if (uri.indexOf("http://") == -1 && uri.indexOf("https://") == -1) { uri = "http://" + uri; } @@ -301,4 +298,4 @@ public class NetworkManager extends Plugin { return reachable; } -} \ No newline at end of file +} diff --git a/framework/src/com/phonegap/api/IPlugin.java b/framework/src/com/phonegap/api/IPlugin.java index b3b3a1da..b9c316e0 100755 --- a/framework/src/com/phonegap/api/IPlugin.java +++ b/framework/src/com/phonegap/api/IPlugin.java @@ -54,13 +54,17 @@ public interface IPlugin { /** * Called when the system is about to start resuming a previous activity. + * + * @param multitasking Flag indicating if multitasking is turned on for app */ - void onPause(); + void onPause(boolean multitasking); /** * Called when the activity will start interacting with the user. + * + * @param multitasking Flag indicating if multitasking is turned on for app */ - void onResume(); + void onResume(boolean multitasking); /** * The final call you receive before your activity is destroyed. diff --git a/framework/src/com/phonegap/api/Plugin.java b/framework/src/com/phonegap/api/Plugin.java index a527159a..746bd408 100755 --- a/framework/src/com/phonegap/api/Plugin.java +++ b/framework/src/com/phonegap/api/Plugin.java @@ -64,14 +64,24 @@ public abstract class Plugin implements IPlugin { /** * Called when the system is about to start resuming a previous activity. + * + * @param multitasking Flag indicating if multitasking is turned on for app */ - public void onPause() { + public void onPause(boolean multitasking) { } /** * Called when the activity will start interacting with the user. + * + * @param multitasking Flag indicating if multitasking is turned on for app */ - public void onResume() { + public void onResume(boolean multitasking) { + } + + /** + * Called when the activity receives a new intent. + */ + public void onNewIntent(Intent intent) { } /** diff --git a/framework/src/com/phonegap/api/PluginManager.java b/framework/src/com/phonegap/api/PluginManager.java index ad1cf20b..393c6a29 100755 --- a/framework/src/com/phonegap/api/PluginManager.java +++ b/framework/src/com/phonegap/api/PluginManager.java @@ -13,6 +13,7 @@ import java.util.Map.Entry; import org.json.JSONArray; import org.json.JSONException; +import android.content.Intent; import android.webkit.WebView; /** @@ -229,27 +230,31 @@ public final class PluginManager { /** * Called when the system is about to start resuming a previous activity. + * + * @param multitasking Flag indicating if multitasking is turned on for app */ - public void onPause() { + public void onPause(boolean multitasking) { java.util.Set> s = this.plugins.entrySet(); java.util.Iterator> it = s.iterator(); while(it.hasNext()) { Entry entry = it.next(); Plugin plugin = entry.getValue(); - plugin.onPause(); + plugin.onPause(multitasking); } } /** * Called when the activity will start interacting with the user. + * + * @param multitasking Flag indicating if multitasking is turned on for app */ - public void onResume() { + public void onResume(boolean multitasking) { java.util.Set> s = this.plugins.entrySet(); java.util.Iterator> it = s.iterator(); while(it.hasNext()) { Entry entry = it.next(); Plugin plugin = entry.getValue(); - plugin.onResume(); + plugin.onResume(multitasking); } } @@ -265,4 +270,17 @@ public final class PluginManager { plugin.onDestroy(); } } + + /** + * Called when the activity receives a new intent. + */ + public void onNewIntent(Intent intent) { + java.util.Set> s = this.plugins.entrySet(); + java.util.Iterator> it = s.iterator(); + while(it.hasNext()) { + Entry entry = it.next(); + Plugin plugin = entry.getValue(); + plugin.onNewIntent(intent); + } + } } \ No newline at end of file diff --git a/lib/classic.rb b/lib/classic.rb index ba6a47f6..4eeb7e53 100755 --- a/lib/classic.rb +++ b/lib/classic.rb @@ -88,7 +88,7 @@ class Classic # copies stuff from src directory into the android project directory (@path) def copy_libs - version = IO.read(File.join(@framework_dir, '../VERSION')) + version = IO.read(File.join(@framework_dir, '../VERSION')).lstrip.rstrip framework_res_dir = File.join(@framework_dir, "res") app_res_dir = File.join(@path, "res") # copies in the jar diff --git a/lib/update.rb b/lib/update.rb index aa1459a1..23e57a70 100755 --- a/lib/update.rb +++ b/lib/update.rb @@ -33,12 +33,19 @@ class Update # TODO need to allow for www import inc icon def copy_libs puts "Copying over libraries and assets..." - - FileUtils.mkdir_p File.join(@path, "libs") - FileUtils.cp File.join(@framework_dir, "phonegap.jar"), File.join(@path, "libs") + version = IO.read(File.join(@framework_dir, '../VERSION')) - FileUtils.mkdir_p File.join(@path, "assets", "www") - FileUtils.cp File.join(@framework_dir, "assets", "www", "phonegap.js"), File.join(@path, "assets", "www") + FileUtils.cp File.join(@framework_dir, "phonegap.#{ version }.jar"), File.join(@path, "libs") + + # concat JS and put into www folder. this can be overridden in the config.xml via @app_js_dir + js_dir = File.join(@framework_dir, "assets", "js") + phonegapjs = IO.read(File.join(js_dir, 'phonegap.js.base')) + Dir.new(js_dir).entries.each do |script| + next if script[0].chr == "." or script == "phonegap.js.base" + phonegapjs << IO.read(File.join(js_dir, script)) + phonegapjs << "\n\n" + end + File.open(File.join(@path, "assets", "www", "phonegap.#{ version }.js"), 'w') {|f| f.write(phonegapjs) } end # end \ No newline at end of file