├── README.md ├── .gitignore ├── assets └── www │ ├── error.html │ ├── index.html │ ├── main.js │ ├── js │ ├── exceptions.js │ ├── websocket.js │ └── phonegap.0.9.5.1.js │ └── chat.html ├── libs └── phonegap.0.9.5.1.jar ├── res ├── drawable-hdpi │ └── icon.png ├── drawable-ldpi │ └── icon.png ├── drawable-mdpi │ └── icon.png ├── values │ └── strings.xml └── layout │ └── main.xml ├── .classpath ├── default.properties ├── gen └── com │ └── phonegap │ └── chat │ └── R.java ├── src └── com │ ├── phonegap │ └── chat │ │ └── App.java │ └── strumsoft │ └── websocket │ └── phonegap │ ├── WebSocketFactory.java │ └── WebSocket.java ├── .project ├── proguard.cfg └── AndroidManifest.xml /README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/* 2 | -------------------------------------------------------------------------------- /assets/www/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ERROR 4 | 5 | -------------------------------------------------------------------------------- /libs/phonegap.0.9.5.1.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brunoarueira/chat-phonegap/HEAD/libs/phonegap.0.9.5.1.jar -------------------------------------------------------------------------------- /res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brunoarueira/chat-phonegap/HEAD/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brunoarueira/chat-phonegap/HEAD/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brunoarueira/chat-phonegap/HEAD/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Chat - PhoneGap 4 | file:///android_assets/www/index.html 5 | 6 | -------------------------------------------------------------------------------- /res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /default.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "build.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-8 12 | -------------------------------------------------------------------------------- /assets/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Chat - Phonegap 7 | 8 | 9 | 10 | chat 11 | 12 | -------------------------------------------------------------------------------- /gen/com/phonegap/chat/R.java: -------------------------------------------------------------------------------- 1 | /* AUTO-GENERATED FILE. DO NOT MODIFY. 2 | * 3 | * This class was automatically generated by the 4 | * aapt tool from the resource data it found. It 5 | * should not be modified by hand. 6 | */ 7 | 8 | package com.phonegap.chat; 9 | 10 | public final class R { 11 | public static final class attr { 12 | } 13 | public static final class drawable { 14 | public static final int icon=0x7f020000; 15 | } 16 | public static final class layout { 17 | public static final int main=0x7f030000; 18 | } 19 | public static final class string { 20 | public static final int app_name=0x7f040000; 21 | public static final int url=0x7f040001; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /assets/www/main.js: -------------------------------------------------------------------------------- 1 | var deviceInfo = function() { 2 | document.getElementById("platform").innerHTML = device.platform; 3 | document.getElementById("version").innerHTML = device.version; 4 | document.getElementById("uuid").innerHTML = device.uuid; 5 | document.getElementById("name").innerHTML = device.name; 6 | document.getElementById("width").innerHTML = screen.width; 7 | document.getElementById("height").innerHTML = screen.height; 8 | document.getElementById("colorDepth").innerHTML = screen.colorDepth; 9 | }; 10 | 11 | var beep = function() { 12 | navigator.notification.beep(2); 13 | }; 14 | 15 | function init() { 16 | // the next line makes it impossible to see Contacts on the HTC Evo since it 17 | // doesn't have a scroll button 18 | // document.addEventListener("touchmove", preventBehavior, false); 19 | document.addEventListener("deviceready", deviceInfo, true); 20 | } 21 | -------------------------------------------------------------------------------- /src/com/phonegap/chat/App.java: -------------------------------------------------------------------------------- 1 | package com.phonegap.chat; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.phonegap.DroidGap; 6 | import com.strumsoft.websocket.phonegap.WebSocketFactory; 7 | 8 | public class App extends DroidGap { 9 | /** Called when the activity is first created. */ 10 | @Override 11 | public void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | super.init(); 14 | 15 | if (appView != null) { 16 | WebSocketFactory wsf = new WebSocketFactory(appView); 17 | appView.addJavascriptInterface(wsf, 18 | "WebSocketFactory"); 19 | super.loadUrl("file:///android_asset/www/index.html"); 20 | /* 21 | * super.loadUrl("file:///android_asset/www/index.html"); 22 | * appView.addJavascriptInterface(new WebSocketFactory(appView), 23 | * "WebSocketFactory"); 24 | */ 25 | } else { 26 | super.loadUrl("file:///android_asset/www/error.html"); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | chat-phonegap 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /proguard.cfg: -------------------------------------------------------------------------------- 1 | -optimizationpasses 5 2 | -dontusemixedcaseclassnames 3 | -dontskipnonpubliclibraryclasses 4 | -dontpreverify 5 | -verbose 6 | -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* 7 | 8 | -keep public class * extends android.app.Activity 9 | -keep public class * extends android.app.Application 10 | -keep public class * extends android.app.Service 11 | -keep public class * extends android.content.BroadcastReceiver 12 | -keep public class * extends android.content.ContentProvider 13 | -keep public class com.android.vending.licensing.ILicensingService 14 | 15 | -keepclasseswithmembernames class * { 16 | native ; 17 | } 18 | 19 | -keepclasseswithmembernames class * { 20 | public (android.content.Context, android.util.AttributeSet); 21 | } 22 | 23 | -keepclasseswithmembernames class * { 24 | public (android.content.Context, android.util.AttributeSet, int); 25 | } 26 | 27 | -keepclassmembers enum * { 28 | public static **[] values(); 29 | public static ** valueOf(java.lang.String); 30 | } 31 | 32 | -keep class * implements android.os.Parcelable { 33 | public static final android.os.Parcelable$Creator *; 34 | } 35 | -------------------------------------------------------------------------------- /assets/www/js/exceptions.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | window.onexception = function(e) { 3 | alert('Got an exception ' + e.message) 4 | }; 5 | 6 | /** 7 | * Wrap function with exception caturer. 8 | * */ 9 | var captureExceptions = function(origFn) { 10 | return function () { 11 | try { 12 | origFn.apply(this, arguments); 13 | } 14 | catch (e) { 15 | window.onexception(e); 16 | throw e; 17 | } 18 | }; 19 | }; 20 | 21 | /** 22 | * Wraps callbacks in object methods 23 | * 24 | * */ 25 | var wrapCallbackFunction = function(obj, fnName, callbackIndex) { 26 | var origFn = obj[fnName]; 27 | 28 | obj[fnName] = function() { 29 | var args = [].slice.call(arguments); 30 | args[callbackIndex] = captureExceptions(args[callbackIndex]); 31 | origFn.apply(this, args); 32 | }; 33 | }; 34 | 35 | wrapCallbackFunction(window, "addEventListener", 1); 36 | wrapCallbackFunction(Node.prototype, "addEventListener", 1); 37 | wrapCallbackFunction(window, "setTimeout", 0); 38 | wrapCallbackFunction(window, "setInterval", 0); 39 | }()); -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/com/strumsoft/websocket/phonegap/WebSocketFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Animesh Kumar (https://github.com/anismiles) 3 | * Copyright (c) 2010 Strumsoft (https://strumsoft.com) 4 | * 5 | * Permission is hereby granted, free of charge, to any person 6 | * obtaining a copy of this software and associated documentation 7 | * files (the "Software"), to deal in the Software without 8 | * restriction, including without limitation the rights to use, 9 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following 12 | * conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | * OTHER DEALINGS IN THE SOFTWARE. 25 | * 26 | */ 27 | package com.strumsoft.websocket.phonegap; 28 | 29 | import java.net.URI; 30 | import java.util.Random; 31 | 32 | import android.webkit.WebView; 33 | 34 | /** 35 | * The WebSocketFactory is like a helper class to instantiate new 36 | * WebSocket instaces especially from Javascript side. It expects a valid 37 | * "ws://" URI. 38 | * 39 | * @author Animesh Kumar 40 | */ 41 | public class WebSocketFactory { 42 | 43 | /** The app view. */ 44 | WebView appView; 45 | 46 | /** 47 | * Instantiates a new web socket factory. 48 | * 49 | * @param appView 50 | * the app view 51 | */ 52 | public WebSocketFactory(WebView appView) { 53 | this.appView = appView; 54 | } 55 | 56 | public WebSocket getInstance(String url) { 57 | // use Draft75 by default 58 | return getInstance(url, WebSocket.Draft.DRAFT75); 59 | } 60 | 61 | public WebSocket getInstance(String url, WebSocket.Draft draft) { 62 | WebSocket socket = null; 63 | Thread th = null; 64 | try { 65 | socket = new WebSocket(appView, new URI(url), draft, getRandonUniqueId()); 66 | th = socket.connect(); 67 | return socket; 68 | } catch (Exception e) { 69 | //Log.v("websocket", e.toString()); 70 | if(th != null) { 71 | th.interrupt(); 72 | } 73 | } 74 | return null; 75 | } 76 | 77 | /** 78 | * Generates random unique ids for WebSocket instances 79 | * 80 | * @return String 81 | */ 82 | private String getRandonUniqueId() { 83 | return "WEBSOCKET." + new Random().nextInt(100); 84 | } 85 | 86 | } -------------------------------------------------------------------------------- /assets/www/js/websocket.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Animesh Kumar (https://github.com/anismiles) 3 | * Copyright (c) 2010 Strumsoft (https://strumsoft.com) 4 | * 5 | * Permission is hereby granted, free of charge, to any person 6 | * obtaining a copy of this software and associated documentation 7 | * files (the "Software"), to deal in the Software without 8 | * restriction, including without limitation the rights to use, 9 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following 12 | * conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | * OTHER DEALINGS IN THE SOFTWARE. 25 | * 26 | */ 27 | (function() { 28 | // window object 29 | var global = window; 30 | 31 | // WebSocket Object. All listener methods are cleaned up! 32 | var WebSocket = global.WebSocket = function(url) { 33 | try { 34 | // get a new websocket object from factory (check com.strumsoft.websocket.WebSocketFactory.java) 35 | this.socket = global.WebSocketFactory.getInstance(url); 36 | 37 | // store in registry 38 | if(this.socket) { 39 | WebSocket.store[this.socket.getId()] = this; 40 | } else { 41 | throw new Error('websocket instantiation failed! Address could be wrong.'); 42 | } 43 | } catch(exception) { 44 | throw exception; 45 | } 46 | }; 47 | 48 | // storage to hold websocket object for later invokation of event methods 49 | WebSocket.store = {}; 50 | 51 | // static event methods to call event methods on target websocket objects 52 | WebSocket.onmessage = function (evt) { 53 | WebSocket.store[evt._target]['onmessage'].call(global, evt._data); 54 | } 55 | 56 | WebSocket.onopen = function (evt) { 57 | WebSocket.store[evt._target]['onopen'].call(global, evt._data); 58 | } 59 | 60 | WebSocket.onclose = function (evt) { 61 | WebSocket.store[evt._target]['onclose'].call(global, evt._data); 62 | } 63 | 64 | WebSocket.onerror = function (evt) { 65 | WebSocket.store[evt._target]['onerror'].call(global, evt._data); 66 | } 67 | 68 | // instance event methods 69 | WebSocket.prototype.send = function(data) { 70 | this.socket.send(data); 71 | } 72 | 73 | WebSocket.prototype.close = function() { 74 | this.socket.close(); 75 | } 76 | 77 | WebSocket.prototype.getReadyState = function() { 78 | this.socket.getReadyState(); 79 | } 80 | ///////////// Must be overloaded 81 | WebSocket.prototype.onopen = function(){ 82 | throw new Error('onopen not implemented.'); 83 | }; 84 | 85 | // alerts message pushed from server 86 | WebSocket.prototype.onmessage = function(msg){ 87 | throw new Error('onmessage not implemented.'); 88 | }; 89 | 90 | // alerts message pushed from server 91 | WebSocket.prototype.onerror = function(msg){ 92 | throw new Error('onerror not implemented.'); 93 | }; 94 | 95 | // alert close event 96 | WebSocket.prototype.onclose = function(){ 97 | throw new Error('onclose not implemented.'); 98 | }; 99 | })(); -------------------------------------------------------------------------------- /assets/www/chat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Chat - Phonegap 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 63 | 64 |

Sample chat client

65 |

Connecting...

66 |
67 | 68 |
69 | 70 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/com/strumsoft/websocket/phonegap/WebSocket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Nathan Rajlich (https://github.com/TooTallNate) 3 | * Copyright (c) 2010 Animesh Kumar (https://github.com/anismiles) 4 | * Copyright (c) 2010 Strumsoft (https://strumsoft.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person 7 | * obtaining a copy of this software and associated documentation 8 | * files (the "Software"), to deal in the Software without 9 | * restriction, including without limitation the rights to use, 10 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following 13 | * conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be 16 | * included in all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | * OTHER DEALINGS IN THE SOFTWARE. 26 | * 27 | */ 28 | package com.strumsoft.websocket.phonegap; 29 | 30 | import java.io.IOException; 31 | import java.io.UnsupportedEncodingException; 32 | import java.net.InetSocketAddress; 33 | import java.net.URI; 34 | import java.nio.ByteBuffer; 35 | import java.nio.channels.NotYetConnectedException; 36 | import java.nio.channels.SelectionKey; 37 | import java.nio.channels.Selector; 38 | import java.nio.channels.SocketChannel; 39 | import java.security.MessageDigest; 40 | import java.security.NoSuchAlgorithmException; 41 | import java.util.Iterator; 42 | import java.util.Random; 43 | import java.util.Set; 44 | import java.util.concurrent.BlockingQueue; 45 | import java.util.concurrent.LinkedBlockingQueue; 46 | 47 | import android.util.Log; 48 | import android.webkit.WebView; 49 | 50 | /** 51 | * The WebSocket is an implementation of WebSocket Client API, and 52 | * expects a valid "ws://" URI to connect to. When connected, an instance 53 | * recieves important events related to the life of the connection, like 54 | * onOpen, onClose, onError and 55 | * onMessage. An instance can send messages to the server via the 56 | * send method. 57 | * 58 | * @author Animesh Kumar 59 | */ 60 | public class WebSocket implements Runnable { 61 | 62 | /** 63 | * Enum for WebSocket Draft 64 | */ 65 | public enum Draft { 66 | DRAFT75, DRAFT76 67 | } 68 | 69 | // //////////////// CONSTANT 70 | /** 71 | * The connection has not yet been established. 72 | */ 73 | public final static int WEBSOCKET_STATE_CONNECTING = 0; 74 | /** 75 | * The WebSocket connection is established and communication is possible. 76 | */ 77 | public final static int WEBSOCKET_STATE_OPEN = 1; 78 | /** 79 | * The connection is going through the closing handshake. 80 | */ 81 | public final static int WEBSOCKET_STATE_CLOSING = 2; 82 | /** 83 | * The connection has been closed or could not be opened. 84 | */ 85 | public final static int WEBSOCKET_STATE_CLOSED = 3; 86 | 87 | /** 88 | * An empty string 89 | */ 90 | private static String BLANK_MESSAGE = ""; 91 | /** 92 | * The javascript method name for onOpen event. 93 | */ 94 | private static String EVENT_ON_OPEN = "onopen"; 95 | /** 96 | * The javascript method name for onMessage event. 97 | */ 98 | private static String EVENT_ON_MESSAGE = "onmessage"; 99 | /** 100 | * The javascript method name for onClose event. 101 | */ 102 | private static String EVENT_ON_CLOSE = "onclose"; 103 | /** 104 | * The javascript method name for onError event. 105 | */ 106 | private static String EVENT_ON_ERROR = "onerror"; 107 | /** 108 | * The default port of WebSockets, as defined in the spec. 109 | */ 110 | public static final int DEFAULT_PORT = 80; 111 | /** 112 | * The WebSocket protocol expects UTF-8 encoded bytes. 113 | */ 114 | public static final String UTF8_CHARSET = "UTF-8"; 115 | /** 116 | * The byte representing Carriage Return, or \r 117 | */ 118 | public static final byte DATA_CR = (byte) 0x0D; 119 | /** 120 | * The byte representing Line Feed, or \n 121 | */ 122 | public static final byte DATA_LF = (byte) 0x0A; 123 | /** 124 | * The byte representing the beginning of a WebSocket text frame. 125 | */ 126 | public static final byte DATA_START_OF_FRAME = (byte) 0x00; 127 | /** 128 | * The byte representing the end of a WebSocket text frame. 129 | */ 130 | public static final byte DATA_END_OF_FRAME = (byte) 0xFF; 131 | 132 | // //////////////// INSTANCE Variables 133 | /** 134 | * The WebView instance from Phonegap DroidGap 135 | */ 136 | private WebView appView; 137 | /** 138 | * The unique id for this instance (helps to bind this to javascript events) 139 | */ 140 | private String id; 141 | /** 142 | * The URI this client is supposed to connect to. 143 | */ 144 | private URI uri; 145 | /** 146 | * The port of the websocket server 147 | */ 148 | private int port; 149 | /** 150 | * The Draft of the WebSocket protocol the Client is adhering to. 151 | */ 152 | private Draft draft; 153 | /** 154 | * The SocketChannel instance to use for this server connection. 155 | * This is used to read and write data to. 156 | */ 157 | private SocketChannel socketChannel; 158 | /** 159 | * The 'Selector' used to get event keys from the underlying socket. 160 | */ 161 | private Selector selector; 162 | /** 163 | * Keeps track of whether or not the client thread should continue running. 164 | */ 165 | private boolean running; 166 | /** 167 | * Internally used to determine whether to recieve data as part of the 168 | * remote handshake, or as part of a text frame. 169 | */ 170 | private boolean handshakeComplete; 171 | /** 172 | * The 1-byte buffer reused throughout the WebSocket connection to read 173 | * data. 174 | */ 175 | private ByteBuffer buffer; 176 | /** 177 | * The bytes that make up the remote handshake. 178 | */ 179 | private ByteBuffer remoteHandshake; 180 | /** 181 | * The bytes that make up the current text frame being read. 182 | */ 183 | private ByteBuffer currentFrame; 184 | /** 185 | * Queue of buffers that need to be sent to the client. 186 | */ 187 | private BlockingQueue bufferQueue; 188 | /** 189 | * Lock object to ensure that data is sent from the bufferQueue in the 190 | * proper order 191 | */ 192 | private Object bufferQueueMutex = new Object(); 193 | /** 194 | * Number 1 used in handshake 195 | */ 196 | private int number1 = 0; 197 | /** 198 | * Number 2 used in handshake 199 | */ 200 | private int number2 = 0; 201 | /** 202 | * Key3 used in handshake 203 | */ 204 | private byte[] key3 = null; 205 | /** 206 | * The readyState attribute represents the state of the connection. 207 | */ 208 | private int readyState = WEBSOCKET_STATE_CONNECTING; 209 | 210 | /** 211 | * Constructor. 212 | * 213 | * Note: this is protected because it's supposed to be instantiated from 214 | * {@link WebSocketFactory} only. 215 | * 216 | * @param appView 217 | * {@link android.webkit.WebView} 218 | * @param uri 219 | * websocket server {@link URI} 220 | * @param draft 221 | * websocket server {@link Draft} implementation (75/76) 222 | * @param id 223 | * unique id for this instance 224 | */ 225 | protected WebSocket(WebView appView, URI uri, Draft draft, String id) { 226 | this.appView = appView; 227 | this.uri = uri; 228 | this.draft = draft; 229 | 230 | // port 231 | port = uri.getPort(); 232 | if (port == -1) { 233 | port = DEFAULT_PORT; 234 | } 235 | 236 | // Id 237 | this.id = id; 238 | 239 | this.bufferQueue = new LinkedBlockingQueue(); 240 | this.handshakeComplete = false; 241 | this.remoteHandshake = this.currentFrame = null; 242 | this.buffer = ByteBuffer.allocate(1); 243 | } 244 | 245 | // ////////////////////////////////////////////////////////////////////////////////////// 246 | // /////////////////////////// WEB SOCKET API Methods 247 | // /////////////////////////////////// 248 | // ////////////////////////////////////////////////////////////////////////////////////// 249 | /** 250 | * Starts a new Thread and connects to server 251 | * 252 | * @throws IOException 253 | */ 254 | public Thread connect() throws IOException { 255 | this.running = true; 256 | this.readyState = WEBSOCKET_STATE_CONNECTING; 257 | // open socket 258 | socketChannel = SocketChannel.open(); 259 | socketChannel.configureBlocking(false); 260 | // set address 261 | socketChannel.connect(new InetSocketAddress(uri.getHost(), port)); 262 | // start a thread to make connection 263 | 264 | // More info: 265 | // http://groups.google.com/group/android-developers/browse_thread/thread/45a8b53e9bf60d82 266 | // http://stackoverflow.com/questions/2879455/android-2-2-and-bad-address-family-on-socket-connect 267 | System.setProperty("java.net.preferIPv4Stack", "true"); 268 | System.setProperty("java.net.preferIPv6Addresses", "false"); 269 | 270 | selector = Selector.open(); 271 | socketChannel.register(selector, SelectionKey.OP_CONNECT); 272 | Log.v("websocket", "Starting a new thread to manage data reading/writing"); 273 | 274 | Thread th = new Thread(this); 275 | th.start(); 276 | // return thread object for explicit closing, if needed 277 | return th; 278 | } 279 | 280 | public void run() { 281 | while (this.running) { 282 | try { 283 | _connect(); 284 | } catch (IOException e) { 285 | this.onError(e); 286 | } 287 | } 288 | } 289 | 290 | /** 291 | * Closes connection with server 292 | */ 293 | public void close() { 294 | this.readyState = WebSocket.WEBSOCKET_STATE_CLOSING; 295 | 296 | // close socket channel 297 | try { 298 | this.socketChannel.close(); 299 | } catch (IOException e) { 300 | this.onError(e); 301 | } 302 | this.running = false; 303 | selector.wakeup(); 304 | 305 | // fire onClose method 306 | this.onClose(); 307 | 308 | this.readyState = WebSocket.WEBSOCKET_STATE_CLOSED; 309 | } 310 | 311 | /** 312 | * Sends text to server 313 | * 314 | * @param text 315 | * String to send to server 316 | */ 317 | public void send(String text) { 318 | try { 319 | if (this.readyState == WEBSOCKET_STATE_OPEN) { 320 | _send(text); 321 | } else { 322 | this.onError(new NotYetConnectedException()); 323 | } 324 | } catch (IOException e) { 325 | this.onError(e); 326 | } 327 | } 328 | 329 | /** 330 | * Called when an entire text frame has been recieved. 331 | * 332 | * @param msg 333 | * Message from websocket server 334 | */ 335 | public void onMessage(String msg) { 336 | appView.loadUrl(buildJavaScriptData(EVENT_ON_MESSAGE, msg)); 337 | } 338 | 339 | public void onOpen() { 340 | appView.loadUrl(buildJavaScriptData(EVENT_ON_OPEN, BLANK_MESSAGE)); 341 | } 342 | 343 | public void onClose() { 344 | appView.loadUrl(buildJavaScriptData(EVENT_ON_CLOSE, BLANK_MESSAGE)); 345 | } 346 | 347 | public void onError(Throwable t) { 348 | String msg = t.getMessage(); 349 | appView.loadUrl(buildJavaScriptData(EVENT_ON_ERROR, msg)); 350 | } 351 | 352 | public String getId() { 353 | return id; 354 | } 355 | 356 | /** 357 | * @return the readyState 358 | */ 359 | public int getReadyState() { 360 | return readyState; 361 | } 362 | 363 | /** 364 | * Builds text for javascript engine to invoke proper event method with 365 | * proper data. 366 | * 367 | * @param event 368 | * websocket event (onOpen, onMessage etc.) 369 | * @param msg 370 | * Text message received from websocket server 371 | * @return 372 | */ 373 | private String buildJavaScriptData(String event, String msg) { 374 | String _d = "javascript:WebSocket." + event + "(" + "{" + "\"_target\":\"" + id + "\"," 375 | + "\"_data\":'" + msg + "'" + "}" + ")"; 376 | return _d; 377 | } 378 | 379 | // ////////////////////////////////////////////////////////////////////////////////////// 380 | // /////////////////////////// WEB SOCKET Internal Methods 381 | // ////////////////////////////// 382 | // ////////////////////////////////////////////////////////////////////////////////////// 383 | 384 | private boolean _send(String text) throws IOException { 385 | if (!this.handshakeComplete) { 386 | throw new NotYetConnectedException(); 387 | } 388 | if (text == null) { 389 | throw new NullPointerException("Cannot send 'null' data to a WebSocket."); 390 | } 391 | 392 | // Get 'text' into a WebSocket "frame" of bytes 393 | byte[] textBytes = text.getBytes(UTF8_CHARSET.toString()); 394 | ByteBuffer b = ByteBuffer.allocate(textBytes.length + 2); 395 | b.put(DATA_START_OF_FRAME); 396 | b.put(textBytes); 397 | b.put(DATA_END_OF_FRAME); 398 | b.rewind(); 399 | 400 | // See if we have any backlog that needs to be sent first 401 | if (_write()) { 402 | // Write the ByteBuffer to the socket 403 | this.socketChannel.write(b); 404 | } 405 | 406 | // If we didn't get it all sent, add it to the buffer of buffers 407 | if (b.remaining() > 0) { 408 | if (!this.bufferQueue.offer(b)) { 409 | throw new IOException("Buffers are full, message could not be sent to" 410 | + this.socketChannel.socket().getRemoteSocketAddress()); 411 | } 412 | return false; 413 | } 414 | return true; 415 | } 416 | 417 | // actual connection logic 418 | private void _connect() throws IOException { 419 | // Continuous loop that is only supposed to end when "close" is called. 420 | 421 | selector.select(); 422 | Set keys = selector.selectedKeys(); 423 | Iterator i = keys.iterator(); 424 | 425 | while (i.hasNext()) { 426 | SelectionKey key = i.next(); 427 | i.remove(); 428 | if (key.isConnectable()) { 429 | if (socketChannel.isConnectionPending()) { 430 | socketChannel.finishConnect(); 431 | } 432 | socketChannel.register(selector, SelectionKey.OP_READ); 433 | _writeHandshake(); 434 | } 435 | if (key.isReadable()) { 436 | try { 437 | _read(); 438 | } catch (NoSuchAlgorithmException nsa) { 439 | this.onError(nsa); 440 | } 441 | } 442 | } 443 | 444 | } 445 | 446 | private void _writeHandshake() throws IOException { 447 | String path = this.uri.getPath(); 448 | if (path.indexOf("/") != 0) { 449 | path = "/" + path; 450 | } 451 | 452 | String host = uri.getHost() + (port != DEFAULT_PORT ? ":" + port : ""); 453 | String origin = "*"; // TODO: Make 'origin' configurable 454 | String request = "GET " + path + " HTTP/1.1\r\n" + "Upgrade: WebSocket\r\n" 455 | + "Connection: Upgrade\r\n" + "Host: " + host + "\r\n" + "Origin: " + origin 456 | + "\r\n"; 457 | 458 | // Add randon keys for Draft76 459 | if (this.draft == Draft.DRAFT76) { 460 | request += "Sec-WebSocket-Key1: " + this._randomKey() + "\r\n"; 461 | request += "Sec-WebSocket-Key2: " + this._randomKey() + "\r\n"; 462 | request += "\r\n"; 463 | this.key3 = new byte[8]; 464 | (new Random()).nextBytes(this.key3); 465 | 466 | //Convert to bytes early so last eight bytes don't get jacked 467 | byte[] bRequest = request.getBytes(UTF8_CHARSET); 468 | 469 | byte[] bToSend = new byte[ bRequest.length + 8]; 470 | 471 | //Copy in the Request bytes 472 | System.arraycopy(bRequest,0,bToSend,0,bRequest.length); 473 | 474 | //Now tack on key3 bytes 475 | System.arraycopy(this.key3, 0, bToSend, bRequest.length, this.key3.length); 476 | 477 | //Now we can send all keys as a single frame 478 | _write(bToSend); 479 | return; 480 | } 481 | 482 | request += "\r\n"; 483 | _write(request.getBytes(UTF8_CHARSET)); 484 | } 485 | 486 | private boolean _write() throws IOException { 487 | synchronized (this.bufferQueueMutex) { 488 | ByteBuffer buffer = this.bufferQueue.peek(); 489 | while (buffer != null) { 490 | this.socketChannel.write(buffer); 491 | if (buffer.remaining() > 0) { 492 | return false; // Didn't finish this buffer. There's more to 493 | // send. 494 | } else { 495 | this.bufferQueue.poll(); // Buffer finished. Remove it. 496 | buffer = this.bufferQueue.peek(); 497 | } 498 | } 499 | return true; 500 | } 501 | } 502 | 503 | private void _write(byte[] bytes) throws IOException { 504 | this.socketChannel.write(ByteBuffer.wrap(bytes)); 505 | } 506 | 507 | private void _read() throws IOException, NoSuchAlgorithmException { 508 | this.buffer.rewind(); 509 | 510 | int bytesRead = -1; 511 | try { 512 | bytesRead = this.socketChannel.read(this.buffer); 513 | } catch (Exception ex) { 514 | } 515 | 516 | if (bytesRead == -1) { 517 | close(); 518 | } else if (bytesRead > 0) { 519 | this.buffer.rewind(); 520 | 521 | if (!this.handshakeComplete) { 522 | _readHandshake(); 523 | } else { 524 | _readFrame(); 525 | } 526 | } 527 | } 528 | 529 | private void _readFrame() throws UnsupportedEncodingException { 530 | byte newestByte = this.buffer.get(); 531 | 532 | if (newestByte == DATA_START_OF_FRAME) { // Beginning of Frame 533 | this.currentFrame = null; 534 | 535 | } else if (newestByte == DATA_END_OF_FRAME) { // End of Frame 536 | String textFrame = null; 537 | // currentFrame will be null if END_OF_FRAME was send directly after 538 | // START_OF_FRAME, thus we will send 'null' as the sent message. 539 | if (this.currentFrame != null) { 540 | textFrame = new String(this.currentFrame.array(), UTF8_CHARSET.toString()); 541 | } 542 | // fire onMessage method 543 | this.onMessage(textFrame); 544 | 545 | } else { // Regular frame data, add to current frame buffer 546 | ByteBuffer frame = ByteBuffer.allocate((this.currentFrame != null ? this.currentFrame 547 | .capacity() : 0) 548 | + this.buffer.capacity()); 549 | if (this.currentFrame != null) { 550 | this.currentFrame.rewind(); 551 | frame.put(this.currentFrame); 552 | } 553 | frame.put(newestByte); 554 | this.currentFrame = frame; 555 | } 556 | } 557 | 558 | private void _readHandshake() throws IOException, NoSuchAlgorithmException { 559 | ByteBuffer ch = ByteBuffer.allocate((this.remoteHandshake != null ? this.remoteHandshake 560 | .capacity() : 0) 561 | + this.buffer.capacity()); 562 | if (this.remoteHandshake != null) { 563 | this.remoteHandshake.rewind(); 564 | ch.put(this.remoteHandshake); 565 | } 566 | ch.put(this.buffer); 567 | this.remoteHandshake = ch; 568 | byte[] h = this.remoteHandshake.array(); 569 | // If the ByteBuffer contains 16 random bytes, and ends with 570 | // 0x0D 0x0A 0x0D 0x0A (or two CRLFs), then the client 571 | // handshake is complete for Draft 76 Client. 572 | if ((h.length >= 20 && h[h.length - 20] == DATA_CR && h[h.length - 19] == DATA_LF 573 | && h[h.length - 18] == DATA_CR && h[h.length - 17] == DATA_LF)) { 574 | _readHandshake(new byte[] { h[h.length - 16], h[h.length - 15], h[h.length - 14], 575 | h[h.length - 13], h[h.length - 12], h[h.length - 11], h[h.length - 10], 576 | h[h.length - 9], h[h.length - 8], h[h.length - 7], h[h.length - 6], 577 | h[h.length - 5], h[h.length - 4], h[h.length - 3], h[h.length - 2], 578 | h[h.length - 1] }); 579 | 580 | // If the ByteBuffer contains 8 random bytes,ends with 581 | // 0x0D 0x0A 0x0D 0x0A (or two CRLFs), and the response 582 | // contains Sec-WebSocket-Key1 then the client 583 | // handshake is complete for Draft 76 Server. 584 | } else if ((h.length >= 12 && h[h.length - 12] == DATA_CR && h[h.length - 11] == DATA_LF 585 | && h[h.length - 10] == DATA_CR && h[h.length - 9] == DATA_LF) 586 | && new String(this.remoteHandshake.array(), UTF8_CHARSET) 587 | .contains("Sec-WebSocket-Key1")) {// ************************ 588 | _readHandshake(new byte[] { h[h.length - 8], h[h.length - 7], h[h.length - 6], 589 | h[h.length - 5], h[h.length - 4], h[h.length - 3], h[h.length - 2], 590 | h[h.length - 1] }); 591 | 592 | // Consider Draft 75, and the Flash Security Policy 593 | // Request edge-case. 594 | } else if ((h.length >= 4 && h[h.length - 4] == DATA_CR && h[h.length - 3] == DATA_LF 595 | && h[h.length - 2] == DATA_CR && h[h.length - 1] == DATA_LF) 596 | && !(new String(this.remoteHandshake.array(), UTF8_CHARSET).contains("Sec")) 597 | || (h.length == 23 && h[h.length - 1] == 0)) { 598 | _readHandshake(null); 599 | } 600 | } 601 | 602 | private void _readHandshake(byte[] handShakeBody) throws IOException, NoSuchAlgorithmException { 603 | // byte[] handshakeBytes = this.remoteHandshake.array(); 604 | // String handshake = new String(handshakeBytes, UTF8_CHARSET); 605 | // TODO: Do some parsing of the returned handshake, and close connection 606 | // in received anything unexpected! 607 | 608 | this.handshakeComplete = true; 609 | boolean isConnectionReady = true; 610 | 611 | if (this.draft == WebSocket.Draft.DRAFT76) { 612 | if (handShakeBody == null) { 613 | isConnectionReady = true; 614 | } 615 | byte[] challenge = new byte[] { (byte) (this.number1 >> 24), 616 | (byte) ((this.number1 << 8) >> 24), (byte) ((this.number1 << 16) >> 24), 617 | (byte) ((this.number1 << 24) >> 24), (byte) (this.number2 >> 24), 618 | (byte) ((this.number2 << 8) >> 24), (byte) ((this.number2 << 16) >> 24), 619 | (byte) ((this.number2 << 24) >> 24), this.key3[0], this.key3[1], this.key3[2], 620 | this.key3[3], this.key3[4], this.key3[5], this.key3[6], this.key3[7] }; 621 | MessageDigest md5 = MessageDigest.getInstance("MD5"); 622 | byte[] expected = md5.digest(challenge); 623 | for (int i = 0; i < handShakeBody.length; i++) { 624 | if (expected[i] != handShakeBody[i]) { 625 | isConnectionReady = true; 626 | } 627 | } 628 | } 629 | 630 | if (isConnectionReady) { 631 | this.readyState = WEBSOCKET_STATE_OPEN; 632 | // fire onOpen method 633 | this.onOpen(); 634 | } else { 635 | close(); 636 | } 637 | } 638 | 639 | private String _randomKey() { 640 | Random r = new Random(); 641 | long maxNumber = 4294967295L; 642 | long spaces = r.nextInt(12) + 1; 643 | int max = new Long(maxNumber / spaces).intValue(); 644 | max = Math.abs(max); 645 | int number = r.nextInt(max) + 1; 646 | if (this.number1 == 0) { 647 | this.number1 = number; 648 | } else { 649 | this.number2 = number; 650 | } 651 | long product = number * spaces; 652 | String key = Long.toString(product); 653 | int numChars = r.nextInt(12); 654 | for (int i = 0; i < numChars; i++) { 655 | int position = r.nextInt(key.length()); 656 | position = Math.abs(position); 657 | char randChar = (char) (r.nextInt(95) + 33); 658 | // exclude numbers here 659 | if (randChar >= 48 && randChar <= 57) { 660 | randChar -= 15; 661 | } 662 | key = new StringBuilder(key).insert(position, randChar).toString(); 663 | } 664 | for (int i = 0; i < spaces; i++) { 665 | int position = r.nextInt(key.length() - 1) + 1; 666 | position = Math.abs(position); 667 | key = new StringBuilder(key).insert(position, "\u0020").toString(); 668 | } 669 | return key; 670 | } 671 | } -------------------------------------------------------------------------------- /assets/www/js/phonegap.0.9.5.1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * PhoneGap is available under *either* the terms of the modified BSD license *or* the 3 | * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. 4 | * 5 | * Copyright (c) 2005-2010, Nitobi Software Inc. 6 | * Copyright (c) 2010-2011, IBM Corporation 7 | */ 8 | 9 | if (typeof PhoneGap === "undefined") { 10 | 11 | /** 12 | * The order of events during page load and PhoneGap startup is as follows: 13 | * 14 | * onDOMContentLoaded Internal event that is received when the web page is loaded and parsed. 15 | * window.onload Body onload event. 16 | * onNativeReady Internal event that indicates the PhoneGap native side is ready. 17 | * onPhoneGapInit Internal event that kicks off creation of all PhoneGap JavaScript objects (runs constructors). 18 | * onPhoneGapReady Internal event fired when all PhoneGap JavaScript objects have been created 19 | * onPhoneGapInfoReady Internal event fired when device properties are available 20 | * onDeviceReady User event fired to indicate that PhoneGap is ready 21 | * onResume User event fired to indicate a start/resume lifecycle event 22 | * onPause User event fired to indicate a pause lifecycle event 23 | * onDestroy Internal event fired when app is being destroyed (User should use window.onunload event, not this one). 24 | * 25 | * The only PhoneGap events that user code should register for are: 26 | * onDeviceReady 27 | * onResume 28 | * 29 | * Listeners can be registered as: 30 | * document.addEventListener("deviceready", myDeviceReadyListener, false); 31 | * document.addEventListener("resume", myResumeListener, false); 32 | * document.addEventListener("pause", myPauseListener, false); 33 | */ 34 | 35 | if (typeof(DeviceInfo) !== 'object') { 36 | var DeviceInfo = {}; 37 | } 38 | 39 | /** 40 | * This represents the PhoneGap API itself, and provides a global namespace for accessing 41 | * information about the state of PhoneGap. 42 | * @class 43 | */ 44 | var PhoneGap = { 45 | queue: { 46 | ready: true, 47 | commands: [], 48 | timer: null 49 | } 50 | }; 51 | 52 | /** 53 | * List of resource files loaded by PhoneGap. 54 | * This is used to ensure JS and other files are loaded only once. 55 | */ 56 | PhoneGap.resources = {base: true}; 57 | 58 | /** 59 | * Determine if resource has been loaded by PhoneGap 60 | * 61 | * @param name 62 | * @return 63 | */ 64 | PhoneGap.hasResource = function(name) { 65 | return PhoneGap.resources[name]; 66 | }; 67 | 68 | /** 69 | * Add a resource to list of loaded resources by PhoneGap 70 | * 71 | * @param name 72 | */ 73 | PhoneGap.addResource = function(name) { 74 | PhoneGap.resources[name] = true; 75 | }; 76 | 77 | /** 78 | * Custom pub-sub channel that can have functions subscribed to it 79 | * @constructor 80 | */ 81 | PhoneGap.Channel = function (type) 82 | { 83 | this.type = type; 84 | this.handlers = {}; 85 | this.guid = 0; 86 | this.fired = false; 87 | this.enabled = true; 88 | }; 89 | 90 | /** 91 | * Subscribes the given function to the channel. Any time that 92 | * Channel.fire is called so too will the function. 93 | * Optionally specify an execution context for the function 94 | * and a guid that can be used to stop subscribing to the channel. 95 | * Returns the guid. 96 | */ 97 | PhoneGap.Channel.prototype.subscribe = function(f, c, g) { 98 | // need a function to call 99 | if (f === null) { return; } 100 | 101 | var func = f; 102 | if (typeof c === "object" && f instanceof Function) { func = PhoneGap.close(c, f); } 103 | 104 | g = g || func.observer_guid || f.observer_guid || this.guid++; 105 | func.observer_guid = g; 106 | f.observer_guid = g; 107 | this.handlers[g] = func; 108 | return g; 109 | }; 110 | 111 | /** 112 | * Like subscribe but the function is only called once and then it 113 | * auto-unsubscribes itself. 114 | */ 115 | PhoneGap.Channel.prototype.subscribeOnce = function(f, c) { 116 | var g = null; 117 | var _this = this; 118 | var m = function() { 119 | f.apply(c || null, arguments); 120 | _this.unsubscribe(g); 121 | }; 122 | if (this.fired) { 123 | if (typeof c === "object" && f instanceof Function) { f = PhoneGap.close(c, f); } 124 | f.apply(this, this.fireArgs); 125 | } else { 126 | g = this.subscribe(m); 127 | } 128 | return g; 129 | }; 130 | 131 | /** 132 | * Unsubscribes the function with the given guid from the channel. 133 | */ 134 | PhoneGap.Channel.prototype.unsubscribe = function(g) { 135 | if (g instanceof Function) { g = g.observer_guid; } 136 | this.handlers[g] = null; 137 | delete this.handlers[g]; 138 | }; 139 | 140 | /** 141 | * Calls all functions subscribed to this channel. 142 | */ 143 | PhoneGap.Channel.prototype.fire = function(e) { 144 | if (this.enabled) { 145 | var fail = false; 146 | var item, handler, rv; 147 | for (item in this.handlers) { 148 | if (this.handlers.hasOwnProperty(item)) { 149 | handler = this.handlers[item]; 150 | if (handler instanceof Function) { 151 | rv = (handler.apply(this, arguments) === false); 152 | fail = fail || rv; 153 | } 154 | } 155 | } 156 | this.fired = true; 157 | this.fireArgs = arguments; 158 | return !fail; 159 | } 160 | return true; 161 | }; 162 | 163 | /** 164 | * Calls the provided function only after all of the channels specified 165 | * have been fired. 166 | */ 167 | PhoneGap.Channel.join = function(h, c) { 168 | var i = c.length; 169 | var f = function() { 170 | if (!(--i)) { 171 | h(); 172 | } 173 | }; 174 | var len = i; 175 | var j; 176 | for (j=0; j 219 | * 220 | * @param name The plugin name 221 | * @param obj The plugin object 222 | */ 223 | PhoneGap.addPlugin = function(name, obj) { 224 | if (!window.plugins[name]) { 225 | window.plugins[name] = obj; 226 | } 227 | else { 228 | console.log("Error: Plugin "+name+" already exists."); 229 | } 230 | }; 231 | 232 | /** 233 | * onDOMContentLoaded channel is fired when the DOM content 234 | * of the page has been parsed. 235 | */ 236 | PhoneGap.onDOMContentLoaded = new PhoneGap.Channel('onDOMContentLoaded'); 237 | 238 | /** 239 | * onNativeReady channel is fired when the PhoneGap native code 240 | * has been initialized. 241 | */ 242 | PhoneGap.onNativeReady = new PhoneGap.Channel('onNativeReady'); 243 | 244 | /** 245 | * onPhoneGapInit channel is fired when the web page is fully loaded and 246 | * PhoneGap native code has been initialized. 247 | */ 248 | PhoneGap.onPhoneGapInit = new PhoneGap.Channel('onPhoneGapInit'); 249 | 250 | /** 251 | * onPhoneGapReady channel is fired when the JS PhoneGap objects have been created. 252 | */ 253 | PhoneGap.onPhoneGapReady = new PhoneGap.Channel('onPhoneGapReady'); 254 | 255 | /** 256 | * onPhoneGapInfoReady channel is fired when the PhoneGap device properties 257 | * has been set. 258 | */ 259 | PhoneGap.onPhoneGapInfoReady = new PhoneGap.Channel('onPhoneGapInfoReady'); 260 | 261 | /** 262 | * onPhoneGapConnectionReady channel is fired when the PhoneGap connection properties 263 | * has been set. 264 | */ 265 | PhoneGap.onPhoneGapConnectionReady = new PhoneGap.Channel('onPhoneGapConnectionReady'); 266 | 267 | /** 268 | * onResume channel is fired when the PhoneGap native code 269 | * resumes. 270 | */ 271 | PhoneGap.onResume = new PhoneGap.Channel('onResume'); 272 | 273 | /** 274 | * onPause channel is fired when the PhoneGap native code 275 | * pauses. 276 | */ 277 | PhoneGap.onPause = new PhoneGap.Channel('onPause'); 278 | 279 | /** 280 | * onDestroy channel is fired when the PhoneGap native code 281 | * is destroyed. It is used internally. 282 | * Window.onunload should be used by the user. 283 | */ 284 | PhoneGap.onDestroy = new PhoneGap.Channel('onDestroy'); 285 | PhoneGap.onDestroy.subscribeOnce(function() { 286 | PhoneGap.shuttingDown = true; 287 | }); 288 | PhoneGap.shuttingDown = false; 289 | 290 | // _nativeReady is global variable that the native side can set 291 | // to signify that the native code is ready. It is a global since 292 | // it may be called before any PhoneGap JS is ready. 293 | if (typeof _nativeReady !== 'undefined') { PhoneGap.onNativeReady.fire(); } 294 | 295 | /** 296 | * onDeviceReady is fired only after all PhoneGap objects are created and 297 | * the device properties are set. 298 | */ 299 | PhoneGap.onDeviceReady = new PhoneGap.Channel('onDeviceReady'); 300 | 301 | 302 | // Array of channels that must fire before "deviceready" is fired 303 | PhoneGap.deviceReadyChannelsArray = [ PhoneGap.onPhoneGapReady, PhoneGap.onPhoneGapInfoReady, PhoneGap.onPhoneGapConnectionReady]; 304 | 305 | // Hashtable of user defined channels that must also fire before "deviceready" is fired 306 | PhoneGap.deviceReadyChannelsMap = {}; 307 | 308 | /** 309 | * Indicate that a feature needs to be initialized before it is ready to be used. 310 | * This holds up PhoneGap's "deviceready" event until the feature has been initialized 311 | * and PhoneGap.initComplete(feature) is called. 312 | * 313 | * @param feature {String} The unique feature name 314 | */ 315 | PhoneGap.waitForInitialization = function(feature) { 316 | if (feature) { 317 | var channel = new PhoneGap.Channel(feature); 318 | PhoneGap.deviceReadyChannelsMap[feature] = channel; 319 | PhoneGap.deviceReadyChannelsArray.push(channel); 320 | } 321 | }; 322 | 323 | /** 324 | * Indicate that initialization code has completed and the feature is ready to be used. 325 | * 326 | * @param feature {String} The unique feature name 327 | */ 328 | PhoneGap.initializationComplete = function(feature) { 329 | var channel = PhoneGap.deviceReadyChannelsMap[feature]; 330 | if (channel) { 331 | channel.fire(); 332 | } 333 | }; 334 | 335 | /** 336 | * Create all PhoneGap objects once page has fully loaded and native side is ready. 337 | */ 338 | PhoneGap.Channel.join(function() { 339 | 340 | // Start listening for XHR callbacks 341 | setTimeout(function() { 342 | if (PhoneGap.UsePolling) { 343 | PhoneGap.JSCallbackPolling(); 344 | } 345 | else { 346 | var polling = prompt("usePolling", "gap_callbackServer:"); 347 | if (polling == "true") { 348 | PhoneGap.JSCallbackPolling(); 349 | } 350 | else { 351 | PhoneGap.JSCallback(); 352 | } 353 | } 354 | }, 1); 355 | 356 | // Run PhoneGap constructors 357 | PhoneGap.onPhoneGapInit.fire(); 358 | 359 | // Fire event to notify that all objects are created 360 | PhoneGap.onPhoneGapReady.fire(); 361 | 362 | // Fire onDeviceReady event once all constructors have run and PhoneGap info has been 363 | // received from native side, and any user defined initialization channels. 364 | PhoneGap.Channel.join(function() { 365 | 366 | // Turn off app loading dialog 367 | navigator.notification.activityStop(); 368 | 369 | PhoneGap.onDeviceReady.fire(); 370 | 371 | // Fire the onresume event, since first one happens before JavaScript is loaded 372 | PhoneGap.onResume.fire(); 373 | }, PhoneGap.deviceReadyChannelsArray); 374 | 375 | }, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]); 376 | 377 | // Listen for DOMContentLoaded and notify our channel subscribers 378 | document.addEventListener('DOMContentLoaded', function() { 379 | PhoneGap.onDOMContentLoaded.fire(); 380 | }, false); 381 | 382 | // Intercept calls to document.addEventListener and watch for deviceready 383 | PhoneGap.m_document_addEventListener = document.addEventListener; 384 | 385 | document.addEventListener = function(evt, handler, capture) { 386 | var e = evt.toLowerCase(); 387 | if (e === 'deviceready') { 388 | PhoneGap.onDeviceReady.subscribeOnce(handler); 389 | } else if (e === 'resume') { 390 | PhoneGap.onResume.subscribe(handler); 391 | if (PhoneGap.onDeviceReady.fired) { 392 | PhoneGap.onResume.fire(); 393 | } 394 | } else if (e === 'pause') { 395 | PhoneGap.onPause.subscribe(handler); 396 | } 397 | else { 398 | // If subscribing to Android backbutton 399 | if (e === 'backbutton') { 400 | PhoneGap.exec(null, null, "App", "overrideBackbutton", [true]); 401 | } 402 | 403 | PhoneGap.m_document_addEventListener.call(document, evt, handler, capture); 404 | } 405 | }; 406 | 407 | // Intercept calls to document.removeEventListener and watch for events that 408 | // are generated by PhoneGap native code 409 | PhoneGap.m_document_removeEventListener = document.removeEventListener; 410 | 411 | document.removeEventListener = function(evt, handler, capture) { 412 | var e = evt.toLowerCase(); 413 | 414 | // If unsubscribing to Android backbutton 415 | if (e === 'backbutton') { 416 | PhoneGap.exec(null, null, "App", "overrideBackbutton", [false]); 417 | } 418 | 419 | PhoneGap.m_document_removeEventListener.call(document, evt, handler, capture); 420 | }; 421 | 422 | /** 423 | * Method to fire event from native code 424 | */ 425 | PhoneGap.fireEvent = function(type) { 426 | var e = document.createEvent('Events'); 427 | e.initEvent(type); 428 | document.dispatchEvent(e); 429 | }; 430 | 431 | /** 432 | * If JSON not included, use our own stringify. (Android 1.6) 433 | * The restriction on ours is that it must be an array of simple types. 434 | * 435 | * @param args 436 | * @return {String} 437 | */ 438 | PhoneGap.stringify = function(args) { 439 | if (typeof JSON === "undefined") { 440 | var s = "["; 441 | var i, type, start, name, nameType, a; 442 | for (i = 0; i < args.length; i++) { 443 | if (args[i] !== null) { 444 | if (i > 0) { 445 | s = s + ","; 446 | } 447 | type = typeof args[i]; 448 | if ((type === "number") || (type === "boolean")) { 449 | s = s + args[i]; 450 | } else if (args[i] instanceof Array) { 451 | s = s + "[" + args[i] + "]"; 452 | } else if (args[i] instanceof Object) { 453 | start = true; 454 | s = s + '{'; 455 | for (name in args[i]) { 456 | if (args[i][name] !== null) { 457 | if (!start) { 458 | s = s + ','; 459 | } 460 | s = s + '"' + name + '":'; 461 | nameType = typeof args[i][name]; 462 | if ((nameType === "number") || (nameType === "boolean")) { 463 | s = s + args[i][name]; 464 | } else if ((typeof args[i][name]) === 'function') { 465 | // don't copy the functions 466 | s = s + '""'; 467 | } else if (args[i][name] instanceof Object) { 468 | s = s + PhoneGap.stringify(args[i][name]); 469 | } else { 470 | s = s + '"' + args[i][name] + '"'; 471 | } 472 | start = false; 473 | } 474 | } 475 | s = s + '}'; 476 | } else { 477 | a = args[i].replace(/\\/g, '\\\\'); 478 | a = a.replace(/"/g, '\\"'); 479 | s = s + '"' + a + '"'; 480 | } 481 | } 482 | } 483 | s = s + "]"; 484 | return s; 485 | } else { 486 | return JSON.stringify(args); 487 | } 488 | }; 489 | 490 | /** 491 | * Does a deep clone of the object. 492 | * 493 | * @param obj 494 | * @return {Object} 495 | */ 496 | PhoneGap.clone = function(obj) { 497 | var i, retVal; 498 | if(!obj) { 499 | return obj; 500 | } 501 | 502 | if(obj instanceof Array){ 503 | retVal = []; 504 | for(i = 0; i < obj.length; ++i){ 505 | retVal.push(PhoneGap.clone(obj[i])); 506 | } 507 | return retVal; 508 | } 509 | 510 | if (obj instanceof Function) { 511 | return obj; 512 | } 513 | 514 | if(!(obj instanceof Object)){ 515 | return obj; 516 | } 517 | 518 | if (obj instanceof Date) { 519 | return obj; 520 | } 521 | 522 | retVal = {}; 523 | for(i in obj){ 524 | if(!(i in retVal) || retVal[i] !== obj[i]) { 525 | retVal[i] = PhoneGap.clone(obj[i]); 526 | } 527 | } 528 | return retVal; 529 | }; 530 | 531 | PhoneGap.callbackId = 0; 532 | PhoneGap.callbacks = {}; 533 | PhoneGap.callbackStatus = { 534 | NO_RESULT: 0, 535 | OK: 1, 536 | CLASS_NOT_FOUND_EXCEPTION: 2, 537 | ILLEGAL_ACCESS_EXCEPTION: 3, 538 | INSTANTIATION_EXCEPTION: 4, 539 | MALFORMED_URL_EXCEPTION: 5, 540 | IO_EXCEPTION: 6, 541 | INVALID_ACTION: 7, 542 | JSON_EXCEPTION: 8, 543 | ERROR: 9 544 | }; 545 | 546 | 547 | /** 548 | * Execute a PhoneGap command. It is up to the native side whether this action is synch or async. 549 | * The native side can return: 550 | * Synchronous: PluginResult object as a JSON string 551 | * Asynchrounous: Empty string "" 552 | * If async, the native side will PhoneGap.callbackSuccess or PhoneGap.callbackError, 553 | * depending upon the result of the action. 554 | * 555 | * @param {Function} success The success callback 556 | * @param {Function} fail The fail callback 557 | * @param {String} service The name of the service to use 558 | * @param {String} action Action to be run in PhoneGap 559 | * @param {Array.} [args] Zero or more arguments to pass to the method 560 | */ 561 | PhoneGap.exec = function(success, fail, service, action, args) { 562 | try { 563 | var callbackId = service + PhoneGap.callbackId++; 564 | if (success || fail) { 565 | PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; 566 | } 567 | 568 | var r = prompt(PhoneGap.stringify(args), "gap:"+PhoneGap.stringify([service, action, callbackId, true])); 569 | 570 | // If a result was returned 571 | if (r.length > 0) { 572 | eval("var v="+r+";"); 573 | 574 | // If status is OK, then return value back to caller 575 | if (v.status === PhoneGap.callbackStatus.OK) { 576 | 577 | // If there is a success callback, then call it now with 578 | // returned value 579 | if (success) { 580 | try { 581 | success(v.message); 582 | } catch (e) { 583 | console.log("Error in success callback: " + callbackId + " = " + e); 584 | } 585 | 586 | // Clear callback if not expecting any more results 587 | if (!v.keepCallback) { 588 | delete PhoneGap.callbacks[callbackId]; 589 | } 590 | } 591 | return v.message; 592 | } 593 | 594 | // If no result 595 | else if (v.status === PhoneGap.callbackStatus.NO_RESULT) { 596 | 597 | // Clear callback if not expecting any more results 598 | if (!v.keepCallback) { 599 | delete PhoneGap.callbacks[callbackId]; 600 | } 601 | } 602 | 603 | // If error, then display error 604 | else { 605 | console.log("Error: Status="+v.status+" Message="+v.message); 606 | 607 | // If there is a fail callback, then call it now with returned value 608 | if (fail) { 609 | try { 610 | fail(v.message); 611 | } 612 | catch (e1) { 613 | console.log("Error in error callback: "+callbackId+" = "+e1); 614 | } 615 | 616 | // Clear callback if not expecting any more results 617 | if (!v.keepCallback) { 618 | delete PhoneGap.callbacks[callbackId]; 619 | } 620 | } 621 | return null; 622 | } 623 | } 624 | } catch (e2) { 625 | console.log("Error: "+e2); 626 | } 627 | }; 628 | 629 | /** 630 | * Called by native code when returning successful result from an action. 631 | * 632 | * @param callbackId 633 | * @param args 634 | */ 635 | PhoneGap.callbackSuccess = function(callbackId, args) { 636 | if (PhoneGap.callbacks[callbackId]) { 637 | 638 | // If result is to be sent to callback 639 | if (args.status === PhoneGap.callbackStatus.OK) { 640 | try { 641 | if (PhoneGap.callbacks[callbackId].success) { 642 | PhoneGap.callbacks[callbackId].success(args.message); 643 | } 644 | } 645 | catch (e) { 646 | console.log("Error in success callback: "+callbackId+" = "+e); 647 | } 648 | } 649 | 650 | // Clear callback if not expecting any more results 651 | if (!args.keepCallback) { 652 | delete PhoneGap.callbacks[callbackId]; 653 | } 654 | } 655 | }; 656 | 657 | /** 658 | * Called by native code when returning error result from an action. 659 | * 660 | * @param callbackId 661 | * @param args 662 | */ 663 | PhoneGap.callbackError = function(callbackId, args) { 664 | if (PhoneGap.callbacks[callbackId]) { 665 | try { 666 | if (PhoneGap.callbacks[callbackId].fail) { 667 | PhoneGap.callbacks[callbackId].fail(args.message); 668 | } 669 | } 670 | catch (e) { 671 | console.log("Error in error callback: "+callbackId+" = "+e); 672 | } 673 | 674 | // Clear callback if not expecting any more results 675 | if (!args.keepCallback) { 676 | delete PhoneGap.callbacks[callbackId]; 677 | } 678 | } 679 | }; 680 | 681 | 682 | /** 683 | * Internal function used to dispatch the request to PhoneGap. It processes the 684 | * command queue and executes the next command on the list. If one of the 685 | * arguments is a JavaScript object, it will be passed on the QueryString of the 686 | * url, which will be turned into a dictionary on the other end. 687 | * @private 688 | */ 689 | // TODO: Is this used? 690 | PhoneGap.run_command = function() { 691 | if (!PhoneGap.available || !PhoneGap.queue.ready) { 692 | return; 693 | } 694 | PhoneGap.queue.ready = false; 695 | 696 | var args = PhoneGap.queue.commands.shift(); 697 | if (PhoneGap.queue.commands.length === 0) { 698 | clearInterval(PhoneGap.queue.timer); 699 | PhoneGap.queue.timer = null; 700 | } 701 | 702 | var uri = []; 703 | var dict = null; 704 | var i; 705 | for (i = 1; i < args.length; i++) { 706 | var arg = args[i]; 707 | if (arg === undefined || arg === null) { 708 | arg = ''; 709 | } 710 | if (typeof(arg) === 'object') { 711 | dict = arg; 712 | } else { 713 | uri.push(encodeURIComponent(arg)); 714 | } 715 | } 716 | var url = "gap://" + args[0] + "/" + uri.join("/"); 717 | if (dict !== null) { 718 | var name; 719 | var query_args = []; 720 | for (name in dict) { 721 | if (dict.hasOwnProperty(name) && (typeof (name) === 'string')) { 722 | query_args.push(encodeURIComponent(name) + "=" + encodeURIComponent(dict[name])); 723 | } 724 | } 725 | if (query_args.length > 0) { 726 | url += "?" + query_args.join("&"); 727 | } 728 | } 729 | document.location = url; 730 | 731 | }; 732 | 733 | PhoneGap.JSCallbackPort = null; 734 | PhoneGap.JSCallbackToken = null; 735 | 736 | /** 737 | * This is only for Android. 738 | * 739 | * Internal function that uses XHR to call into PhoneGap Java code and retrieve 740 | * any JavaScript code that needs to be run. This is used for callbacks from 741 | * Java to JavaScript. 742 | */ 743 | PhoneGap.JSCallback = function() { 744 | 745 | // Exit if shutting down app 746 | if (PhoneGap.shuttingDown) { 747 | return; 748 | } 749 | 750 | // If polling flag was changed, start using polling from now on 751 | if (PhoneGap.UsePolling) { 752 | PhoneGap.JSCallbackPolling(); 753 | return; 754 | } 755 | 756 | var xmlhttp = new XMLHttpRequest(); 757 | 758 | // Callback function when XMLHttpRequest is ready 759 | xmlhttp.onreadystatechange=function(){ 760 | if(xmlhttp.readyState === 4){ 761 | 762 | // Exit if shutting down app 763 | if (PhoneGap.shuttingDown) { 764 | return; 765 | } 766 | 767 | // If callback has JavaScript statement to execute 768 | if (xmlhttp.status === 200) { 769 | 770 | // Need to url decode the response and replace %20 with a space 771 | var msg = decodeURIComponent(xmlhttp.responseText.replace(/\+/g, '%20')); 772 | setTimeout(function() { 773 | try { 774 | var t = eval(msg); 775 | } 776 | catch (e) { 777 | // If we're getting an error here, seeing the message will help in debugging 778 | console.log("JSCallback: Message from Server: " + msg); 779 | console.log("JSCallback Error: "+e); 780 | } 781 | }, 1); 782 | setTimeout(PhoneGap.JSCallback, 1); 783 | } 784 | 785 | // If callback ping (used to keep XHR request from timing out) 786 | else if (xmlhttp.status === 404) { 787 | setTimeout(PhoneGap.JSCallback, 10); 788 | } 789 | 790 | // If security error 791 | else if (xmlhttp.status === 403) { 792 | console.log("JSCallback Error: Invalid token. Stopping callbacks."); 793 | } 794 | 795 | // If server is stopping 796 | else if (xmlhttp.status === 503) { 797 | console.log("JSCallback Error: Service unavailable. Stopping callbacks."); 798 | } 799 | 800 | // If request wasn't GET 801 | else if (xmlhttp.status === 400) { 802 | console.log("JSCallback Error: Bad request. Stopping callbacks."); 803 | } 804 | 805 | // If error, restart callback server 806 | else { 807 | console.log("JSCallback Error: Request failed."); 808 | prompt("restartServer", "gap_callbackServer:"); 809 | PhoneGap.JSCallbackPort = null; 810 | PhoneGap.JSCallbackToken = null; 811 | setTimeout(PhoneGap.JSCallback, 100); 812 | } 813 | } 814 | }; 815 | 816 | if (PhoneGap.JSCallbackPort === null) { 817 | PhoneGap.JSCallbackPort = prompt("getPort", "gap_callbackServer:"); 818 | } 819 | if (PhoneGap.JSCallbackToken === null) { 820 | PhoneGap.JSCallbackToken = prompt("getToken", "gap_callbackServer:"); 821 | } 822 | xmlhttp.open("GET", "http://127.0.0.1:"+PhoneGap.JSCallbackPort+"/"+PhoneGap.JSCallbackToken , true); 823 | xmlhttp.send(); 824 | }; 825 | 826 | /** 827 | * The polling period to use with JSCallbackPolling. 828 | * This can be changed by the application. The default is 50ms. 829 | */ 830 | PhoneGap.JSCallbackPollingPeriod = 50; 831 | 832 | /** 833 | * Flag that can be set by the user to force polling to be used or force XHR to be used. 834 | */ 835 | PhoneGap.UsePolling = false; // T=use polling, F=use XHR 836 | 837 | /** 838 | * This is only for Android. 839 | * 840 | * Internal function that uses polling to call into PhoneGap Java code and retrieve 841 | * any JavaScript code that needs to be run. This is used for callbacks from 842 | * Java to JavaScript. 843 | */ 844 | PhoneGap.JSCallbackPolling = function() { 845 | 846 | // Exit if shutting down app 847 | if (PhoneGap.shuttingDown) { 848 | return; 849 | } 850 | 851 | // If polling flag was changed, stop using polling from now on 852 | if (!PhoneGap.UsePolling) { 853 | PhoneGap.JSCallback(); 854 | return; 855 | } 856 | 857 | var msg = prompt("", "gap_poll:"); 858 | if (msg) { 859 | setTimeout(function() { 860 | try { 861 | var t = eval(""+msg); 862 | } 863 | catch (e) { 864 | console.log("JSCallbackPolling: Message from Server: " + msg); 865 | console.log("JSCallbackPolling Error: "+e); 866 | } 867 | }, 1); 868 | setTimeout(PhoneGap.JSCallbackPolling, 1); 869 | } 870 | else { 871 | setTimeout(PhoneGap.JSCallbackPolling, PhoneGap.JSCallbackPollingPeriod); 872 | } 873 | }; 874 | 875 | /** 876 | * Create a UUID 877 | * 878 | * @return {String} 879 | */ 880 | PhoneGap.createUUID = function() { 881 | return PhoneGap.UUIDcreatePart(4) + '-' + 882 | PhoneGap.UUIDcreatePart(2) + '-' + 883 | PhoneGap.UUIDcreatePart(2) + '-' + 884 | PhoneGap.UUIDcreatePart(2) + '-' + 885 | PhoneGap.UUIDcreatePart(6); 886 | }; 887 | 888 | PhoneGap.UUIDcreatePart = function(length) { 889 | var uuidpart = ""; 890 | var i, uuidchar; 891 | for (i=0; i frequency + 10 sec 1032 | PhoneGap.exec( 1033 | function(timeout) { 1034 | if (timeout < (frequency + 10000)) { 1035 | PhoneGap.exec(null, null, "Accelerometer", "setTimeout", [frequency + 10000]); 1036 | } 1037 | }, 1038 | function(e) { }, "Accelerometer", "getTimeout", []); 1039 | 1040 | // Start watch timer 1041 | var id = PhoneGap.createUUID(); 1042 | navigator.accelerometer.timers[id] = setInterval(function() { 1043 | PhoneGap.exec(successCallback, errorCallback, "Accelerometer", "getAcceleration", []); 1044 | }, (frequency ? frequency : 1)); 1045 | 1046 | return id; 1047 | }; 1048 | 1049 | /** 1050 | * Clears the specified accelerometer watch. 1051 | * 1052 | * @param {String} id The id of the watch returned from #watchAcceleration. 1053 | */ 1054 | Accelerometer.prototype.clearWatch = function(id) { 1055 | 1056 | // Stop javascript timer & remove from timer list 1057 | if (id && navigator.accelerometer.timers[id] !== undefined) { 1058 | clearInterval(navigator.accelerometer.timers[id]); 1059 | delete navigator.accelerometer.timers[id]; 1060 | } 1061 | }; 1062 | 1063 | PhoneGap.addConstructor(function() { 1064 | if (typeof navigator.accelerometer === "undefined") { 1065 | navigator.accelerometer = new Accelerometer(); 1066 | } 1067 | }); 1068 | } 1069 | 1070 | 1071 | /* 1072 | * PhoneGap is available under *either* the terms of the modified BSD license *or* the 1073 | * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. 1074 | * 1075 | * Copyright (c) 2005-2010, Nitobi Software Inc. 1076 | * Copyright (c) 2010-2011, IBM Corporation 1077 | */ 1078 | 1079 | if (!PhoneGap.hasResource("app")) { 1080 | PhoneGap.addResource("app"); 1081 | 1082 | /** 1083 | * Constructor 1084 | * @constructor 1085 | */ 1086 | var App = function() {}; 1087 | 1088 | /** 1089 | * Clear the resource cache. 1090 | */ 1091 | App.prototype.clearCache = function() { 1092 | PhoneGap.exec(null, null, "App", "clearCache", []); 1093 | }; 1094 | 1095 | /** 1096 | * Load the url into the webview. 1097 | * 1098 | * @param url The URL to load 1099 | * @param props Properties that can be passed in to the activity: 1100 | * wait: int => wait msec before loading URL 1101 | * loadingDialog: "Title,Message" => display a native loading dialog 1102 | * hideLoadingDialogOnPage: boolean => hide loadingDialog when page loaded instead of when deviceready event occurs. 1103 | * loadInWebView: boolean => cause all links on web page to be loaded into existing web view, instead of being loaded into new browser. 1104 | * loadUrlTimeoutValue: int => time in msec to wait before triggering a timeout error 1105 | * errorUrl: URL => URL to load if there's an error loading specified URL with loadUrl(). Should be a local URL such as file:///android_asset/www/error.html"); 1106 | * keepRunning: boolean => enable app to keep running in background 1107 | * 1108 | * Example: 1109 | * App app = new App(); 1110 | * app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000}); 1111 | */ 1112 | App.prototype.loadUrl = function(url, props) { 1113 | PhoneGap.exec(null, null, "App", "loadUrl", [url, props]); 1114 | }; 1115 | 1116 | /** 1117 | * Cancel loadUrl that is waiting to be loaded. 1118 | */ 1119 | App.prototype.cancelLoadUrl = function() { 1120 | PhoneGap.exec(null, null, "App", "cancelLoadUrl", []); 1121 | }; 1122 | 1123 | /** 1124 | * Clear web history in this web view. 1125 | * Instead of BACK button loading the previous web page, it will exit the app. 1126 | */ 1127 | App.prototype.clearHistory = function() { 1128 | PhoneGap.exec(null, null, "App", "clearHistory", []); 1129 | }; 1130 | 1131 | /** 1132 | * Add a class that implements a service. 1133 | * 1134 | * @param serviceType 1135 | * @param className 1136 | */ 1137 | App.prototype.addService = function(serviceType, className) { 1138 | PhoneGap.exec(null, null, "App", "addService", [serviceType, className]); 1139 | }; 1140 | 1141 | /** 1142 | * Override the default behavior of the Android back button. 1143 | * If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired. 1144 | * 1145 | * Note: The user should not have to call this method. Instead, when the user 1146 | * registers for the "backbutton" event, this is automatically done. 1147 | * 1148 | * @param override T=override, F=cancel override 1149 | */ 1150 | App.prototype.overrideBackbutton = function(override) { 1151 | PhoneGap.exec(null, null, "App", "overrideBackbutton", [override]); 1152 | }; 1153 | 1154 | /** 1155 | * Exit and terminate the application. 1156 | */ 1157 | App.prototype.exitApp = function() { 1158 | return PhoneGap.exec(null, null, "App", "exitApp", []); 1159 | }; 1160 | 1161 | PhoneGap.addConstructor(function() { 1162 | navigator.app = window.app = new App(); 1163 | }); 1164 | } 1165 | 1166 | 1167 | /* 1168 | * PhoneGap is available under *either* the terms of the modified BSD license *or* the 1169 | * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. 1170 | * 1171 | * Copyright (c) 2005-2010, Nitobi Software Inc. 1172 | * Copyright (c) 2010-2011, IBM Corporation 1173 | */ 1174 | 1175 | if (!PhoneGap.hasResource("camera")) { 1176 | PhoneGap.addResource("camera"); 1177 | 1178 | /** 1179 | * This class provides access to the device camera. 1180 | * 1181 | * @constructor 1182 | */ 1183 | var Camera = function() { 1184 | this.successCallback = null; 1185 | this.errorCallback = null; 1186 | this.options = null; 1187 | }; 1188 | 1189 | /** 1190 | * Format of image that returned from getPicture. 1191 | * 1192 | * Example: navigator.camera.getPicture(success, fail, 1193 | * { quality: 80, 1194 | * destinationType: Camera.DestinationType.DATA_URL, 1195 | * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) 1196 | */ 1197 | Camera.DestinationType = { 1198 | DATA_URL: 0, // Return base64 encoded string 1199 | FILE_URI: 1 // Return file uri (content://media/external/images/media/2 for Android) 1200 | }; 1201 | Camera.prototype.DestinationType = Camera.DestinationType; 1202 | 1203 | /** 1204 | * Source to getPicture from. 1205 | * 1206 | * Example: navigator.camera.getPicture(success, fail, 1207 | * { quality: 80, 1208 | * destinationType: Camera.DestinationType.DATA_URL, 1209 | * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) 1210 | */ 1211 | Camera.PictureSourceType = { 1212 | PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) 1213 | CAMERA : 1, // Take picture from camera 1214 | SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android) 1215 | }; 1216 | Camera.prototype.PictureSourceType = Camera.PictureSourceType; 1217 | 1218 | /** 1219 | * Gets a picture from source defined by "options.sourceType", and returns the 1220 | * image as defined by the "options.destinationType" option. 1221 | 1222 | * The defaults are sourceType=CAMERA and destinationType=DATA_URL. 1223 | * 1224 | * @param {Function} successCallback 1225 | * @param {Function} errorCallback 1226 | * @param {Object} options 1227 | */ 1228 | Camera.prototype.getPicture = function(successCallback, errorCallback, options) { 1229 | 1230 | // successCallback required 1231 | if (typeof successCallback !== "function") { 1232 | console.log("Camera Error: successCallback is not a function"); 1233 | return; 1234 | } 1235 | 1236 | // errorCallback optional 1237 | if (errorCallback && (typeof errorCallback !== "function")) { 1238 | console.log("Camera Error: errorCallback is not a function"); 1239 | return; 1240 | } 1241 | 1242 | this.options = options; 1243 | var quality = 80; 1244 | if (options.quality) { 1245 | quality = this.options.quality; 1246 | } 1247 | var destinationType = Camera.DestinationType.DATA_URL; 1248 | if (this.options.destinationType) { 1249 | destinationType = this.options.destinationType; 1250 | } 1251 | var sourceType = Camera.PictureSourceType.CAMERA; 1252 | if (typeof this.options.sourceType === "number") { 1253 | sourceType = this.options.sourceType; 1254 | } 1255 | PhoneGap.exec(successCallback, errorCallback, "Camera", "takePicture", [quality, destinationType, sourceType]); 1256 | }; 1257 | 1258 | PhoneGap.addConstructor(function() { 1259 | if (typeof navigator.camera === "undefined") { 1260 | navigator.camera = new Camera(); 1261 | } 1262 | }); 1263 | } 1264 | 1265 | 1266 | /* 1267 | * PhoneGap is available under *either* the terms of the modified BSD license *or* the 1268 | * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. 1269 | * 1270 | * Copyright (c) 2005-2010, Nitobi Software Inc. 1271 | * Copyright (c) 2010-2011, IBM Corporation 1272 | */ 1273 | 1274 | if (!PhoneGap.hasResource("capture")) { 1275 | PhoneGap.addResource("capture"); 1276 | 1277 | /** 1278 | * Represents a single file. 1279 | * 1280 | * name {DOMString} name of the file, without path information 1281 | * fullPath {DOMString} the full path of the file, including the name 1282 | * type {DOMString} mime type 1283 | * lastModifiedDate {Date} last modified date 1284 | * size {Number} size of the file in bytes 1285 | */ 1286 | var MediaFile = function(name, fullPath, type, lastModifiedDate, size){ 1287 | this.name = name || null; 1288 | this.fullPath = fullPath || null; 1289 | this.type = type || null; 1290 | this.lastModifiedDate = lastModifiedDate || null; 1291 | this.size = size || 0; 1292 | }; 1293 | 1294 | /** 1295 | * Launch device camera application for recording video(s). 1296 | * 1297 | * @param {Function} successCB 1298 | * @param {Function} errorCB 1299 | */ 1300 | MediaFile.prototype.getFormatData = function(successCallback, errorCallback){ 1301 | PhoneGap.exec(successCallback, errorCallback, "Capture", "getFormatData", [this.fullPath, this.type]); 1302 | }; 1303 | 1304 | /** 1305 | * MediaFileData encapsulates format information of a media file. 1306 | * 1307 | * @param {DOMString} codecs 1308 | * @param {long} bitrate 1309 | * @param {long} height 1310 | * @param {long} width 1311 | * @param {float} duration 1312 | */ 1313 | var MediaFileData = function(codecs, bitrate, height, width, duration){ 1314 | this.codecs = codecs || null; 1315 | this.bitrate = bitrate || 0; 1316 | this.height = height || 0; 1317 | this.width = width || 0; 1318 | this.duration = duration || 0; 1319 | }; 1320 | 1321 | /** 1322 | * The CaptureError interface encapsulates all errors in the Capture API. 1323 | */ 1324 | var CaptureError = function(){ 1325 | this.code = null; 1326 | }; 1327 | 1328 | // Capture error codes 1329 | CaptureError.CAPTURE_INTERNAL_ERR = 0; 1330 | CaptureError.CAPTURE_APPLICATION_BUSY = 1; 1331 | CaptureError.CAPTURE_INVALID_ARGUMENT = 2; 1332 | CaptureError.CAPTURE_NO_MEDIA_FILES = 3; 1333 | CaptureError.CAPTURE_NOT_SUPPORTED = 20; 1334 | 1335 | /** 1336 | * The Capture interface exposes an interface to the camera and microphone of the hosting device. 1337 | */ 1338 | var Capture = function(){ 1339 | this.supportedAudioModes = []; 1340 | this.supportedImageModes = []; 1341 | this.supportedVideoModes = []; 1342 | }; 1343 | 1344 | /** 1345 | * Launch audio recorder application for recording audio clip(s). 1346 | * 1347 | * @param {Function} successCB 1348 | * @param {Function} errorCB 1349 | * @param {CaptureAudioOptions} options 1350 | */ 1351 | Capture.prototype.captureAudio = function(successCallback, errorCallback, options){ 1352 | PhoneGap.exec(successCallback, errorCallback, "Capture", "captureAudio", [options]); 1353 | }; 1354 | 1355 | /** 1356 | * Launch camera application for taking image(s). 1357 | * 1358 | * @param {Function} successCB 1359 | * @param {Function} errorCB 1360 | * @param {CaptureImageOptions} options 1361 | */ 1362 | Capture.prototype.captureImage = function(successCallback, errorCallback, options){ 1363 | PhoneGap.exec(successCallback, errorCallback, "Capture", "captureImage", [options]); 1364 | }; 1365 | 1366 | /** 1367 | * Launch camera application for taking image(s). 1368 | * 1369 | * @param {Function} successCB 1370 | * @param {Function} errorCB 1371 | * @param {CaptureImageOptions} options 1372 | */ 1373 | Capture.prototype._castMediaFile = function(pluginResult){ 1374 | var mediaFiles = []; 1375 | var i; 1376 | for (i = 0; i < pluginResult.message.length; i++) { 1377 | var mediaFile = new MediaFile(); 1378 | mediaFile.name = pluginResult.message[i].name; 1379 | mediaFile.fullPath = pluginResult.message[i].fullPath; 1380 | mediaFile.type = pluginResult.message[i].type; 1381 | mediaFile.lastModifiedDate = pluginResult.message[i].lastModifiedDate; 1382 | mediaFile.size = pluginResult.message[i].size; 1383 | mediaFiles.push(mediaFile); 1384 | } 1385 | pluginResult.message = mediaFiles; 1386 | return pluginResult; 1387 | }; 1388 | 1389 | /** 1390 | * Launch device camera application for recording video(s). 1391 | * 1392 | * @param {Function} successCB 1393 | * @param {Function} errorCB 1394 | * @param {CaptureVideoOptions} options 1395 | */ 1396 | Capture.prototype.captureVideo = function(successCallback, errorCallback, options){ 1397 | PhoneGap.exec(successCallback, errorCallback, "Capture", "captureVideo", [options]); 1398 | }; 1399 | 1400 | /** 1401 | * Encapsulates a set of parameters that the capture device supports. 1402 | */ 1403 | var ConfigurationData = function(){ 1404 | // The ASCII-encoded string in lower case representing the media type. 1405 | this.type = null; 1406 | // The height attribute represents height of the image or video in pixels. 1407 | // In the case of a sound clip this attribute has value 0. 1408 | this.height = 0; 1409 | // The width attribute represents width of the image or video in pixels. 1410 | // In the case of a sound clip this attribute has value 0 1411 | this.width = 0; 1412 | }; 1413 | 1414 | /** 1415 | * Encapsulates all image capture operation configuration options. 1416 | */ 1417 | var CaptureImageOptions = function(){ 1418 | // Upper limit of images user can take. Value must be equal or greater than 1. 1419 | this.limit = 1; 1420 | // The selected image mode. Must match with one of the elements in supportedImageModes array. 1421 | this.mode = null; 1422 | }; 1423 | 1424 | /** 1425 | * Encapsulates all video capture operation configuration options. 1426 | */ 1427 | var CaptureVideoOptions = function(){ 1428 | // Upper limit of videos user can record. Value must be equal or greater than 1. 1429 | this.limit = 1; 1430 | // Maximum duration of a single video clip in seconds. 1431 | this.duration = 0; 1432 | // The selected video mode. Must match with one of the elements in supportedVideoModes array. 1433 | this.mode = null; 1434 | }; 1435 | 1436 | /** 1437 | * Encapsulates all audio capture operation configuration options. 1438 | */ 1439 | var CaptureAudioOptions = function(){ 1440 | // Upper limit of sound clips user can record. Value must be equal or greater than 1. 1441 | this.limit = 1; 1442 | // Maximum duration of a single sound clip in seconds. 1443 | this.duration = 0; 1444 | // The selected audio mode. Must match with one of the elements in supportedAudioModes array. 1445 | this.mode = null; 1446 | }; 1447 | 1448 | PhoneGap.addConstructor(function(){ 1449 | if (typeof navigator.device === "undefined") { 1450 | navigator.device = window.device = new Device(); 1451 | } 1452 | if (typeof navigator.device.capture === "undefined") { 1453 | navigator.device.capture = window.device.capture = new Capture(); 1454 | } 1455 | }); 1456 | } 1457 | 1458 | /* 1459 | * PhoneGap is available under *either* the terms of the modified BSD license *or* the 1460 | * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. 1461 | * 1462 | * Copyright (c) 2005-2010, Nitobi Software Inc. 1463 | * Copyright (c) 2010-2011, IBM Corporation 1464 | */ 1465 | 1466 | if (!PhoneGap.hasResource("compass")) { 1467 | PhoneGap.addResource("compass"); 1468 | 1469 | /** 1470 | * This class provides access to device Compass data. 1471 | * @constructor 1472 | */ 1473 | var Compass = function() { 1474 | /** 1475 | * The last known Compass position. 1476 | */ 1477 | this.lastHeading = null; 1478 | 1479 | /** 1480 | * List of compass watch timers 1481 | */ 1482 | this.timers = {}; 1483 | }; 1484 | 1485 | Compass.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"]; 1486 | 1487 | /** 1488 | * Asynchronously aquires the current heading. 1489 | * 1490 | * @param {Function} successCallback The function to call when the heading data is available 1491 | * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) 1492 | * @param {PositionOptions} options The options for getting the heading data such as timeout. (OPTIONAL) 1493 | */ 1494 | Compass.prototype.getCurrentHeading = function(successCallback, errorCallback, options) { 1495 | 1496 | // successCallback required 1497 | if (typeof successCallback !== "function") { 1498 | console.log("Compass Error: successCallback is not a function"); 1499 | return; 1500 | } 1501 | 1502 | // errorCallback optional 1503 | if (errorCallback && (typeof errorCallback !== "function")) { 1504 | console.log("Compass Error: errorCallback is not a function"); 1505 | return; 1506 | } 1507 | 1508 | // Get heading 1509 | PhoneGap.exec(successCallback, errorCallback, "Compass", "getHeading", []); 1510 | }; 1511 | 1512 | /** 1513 | * Asynchronously aquires the heading repeatedly at a given interval. 1514 | * 1515 | * @param {Function} successCallback The function to call each time the heading data is available 1516 | * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) 1517 | * @param {HeadingOptions} options The options for getting the heading data such as timeout and the frequency of the watch. (OPTIONAL) 1518 | * @return String The watch id that must be passed to #clearWatch to stop watching. 1519 | */ 1520 | Compass.prototype.watchHeading= function(successCallback, errorCallback, options) { 1521 | 1522 | // Default interval (100 msec) 1523 | var frequency = (options !== undefined) ? options.frequency : 100; 1524 | 1525 | // successCallback required 1526 | if (typeof successCallback !== "function") { 1527 | console.log("Compass Error: successCallback is not a function"); 1528 | return; 1529 | } 1530 | 1531 | // errorCallback optional 1532 | if (errorCallback && (typeof errorCallback !== "function")) { 1533 | console.log("Compass Error: errorCallback is not a function"); 1534 | return; 1535 | } 1536 | 1537 | // Make sure compass timeout > frequency + 10 sec 1538 | PhoneGap.exec( 1539 | function(timeout) { 1540 | if (timeout < (frequency + 10000)) { 1541 | PhoneGap.exec(null, null, "Compass", "setTimeout", [frequency + 10000]); 1542 | } 1543 | }, 1544 | function(e) { }, "Compass", "getTimeout", []); 1545 | 1546 | // Start watch timer to get headings 1547 | var id = PhoneGap.createUUID(); 1548 | navigator.compass.timers[id] = setInterval( 1549 | function() { 1550 | PhoneGap.exec(successCallback, errorCallback, "Compass", "getHeading", []); 1551 | }, (frequency ? frequency : 1)); 1552 | 1553 | return id; 1554 | }; 1555 | 1556 | 1557 | /** 1558 | * Clears the specified heading watch. 1559 | * 1560 | * @param {String} id The ID of the watch returned from #watchHeading. 1561 | */ 1562 | Compass.prototype.clearWatch = function(id) { 1563 | 1564 | // Stop javascript timer & remove from timer list 1565 | if (id && navigator.compass.timers[id]) { 1566 | clearInterval(navigator.compass.timers[id]); 1567 | delete navigator.compass.timers[id]; 1568 | } 1569 | }; 1570 | 1571 | PhoneGap.addConstructor(function() { 1572 | if (typeof navigator.compass === "undefined") { 1573 | navigator.compass = new Compass(); 1574 | } 1575 | }); 1576 | } 1577 | 1578 | 1579 | /* 1580 | * PhoneGap is available under *either* the terms of the modified BSD license *or* the 1581 | * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text. 1582 | * 1583 | * Copyright (c) 2005-2010, Nitobi Software Inc. 1584 | * Copyright (c) 2010-2011, IBM Corporation 1585 | */ 1586 | 1587 | if (!PhoneGap.hasResource("contact")) { 1588 | PhoneGap.addResource("contact"); 1589 | 1590 | /** 1591 | * Contains information about a single contact. 1592 | * @constructor 1593 | * @param {DOMString} id unique identifier 1594 | * @param {DOMString} displayName 1595 | * @param {ContactName} name 1596 | * @param {DOMString} nickname 1597 | * @param {Array.} phoneNumbers array of phone numbers 1598 | * @param {Array.} emails array of email addresses 1599 | * @param {Array.} addresses array of addresses 1600 | * @param {Array.} ims instant messaging user ids 1601 | * @param {Array.} organizations 1602 | * @param {DOMString} revision date contact was last updated 1603 | * @param {DOMString} birthday contact's birthday 1604 | * @param {DOMString} gender contact's gender 1605 | * @param {DOMString} note user notes about contact 1606 | * @param {Array.} photos 1607 | * @param {Array.} categories 1608 | * @param {Array.} urls contact's web sites 1609 | * @param {DOMString} timezone the contacts time zone 1610 | */ 1611 | var Contact = function (id, displayName, name, nickname, phoneNumbers, emails, addresses, 1612 | ims, organizations, revision, birthday, gender, note, photos, categories, urls, timezone) { 1613 | this.id = id || null; 1614 | this.rawId = null; 1615 | this.displayName = displayName || null; 1616 | this.name = name || null; // ContactName 1617 | this.nickname = nickname || null; 1618 | this.phoneNumbers = phoneNumbers || null; // ContactField[] 1619 | this.emails = emails || null; // ContactField[] 1620 | this.addresses = addresses || null; // ContactAddress[] 1621 | this.ims = ims || null; // ContactField[] 1622 | this.organizations = organizations || null; // ContactOrganization[] 1623 | this.revision = revision || null; 1624 | this.birthday = birthday || null; 1625 | this.gender = gender || null; 1626 | this.note = note || null; 1627 | this.photos = photos || null; // ContactField[] 1628 | this.categories = categories || null; // ContactField[] 1629 | this.urls = urls || null; // ContactField[] 1630 | this.timezone = timezone || null; 1631 | }; 1632 | 1633 | /** 1634 | * ContactError. 1635 | * An error code assigned by an implementation when an error has occurreds 1636 | * @constructor 1637 | */ 1638 | var ContactError = function() { 1639 | this.code=null; 1640 | }; 1641 | 1642 | /** 1643 | * Error codes 1644 | */ 1645 | ContactError.UNKNOWN_ERROR = 0; 1646 | ContactError.INVALID_ARGUMENT_ERROR = 1; 1647 | ContactError.NOT_FOUND_ERROR = 2; 1648 | ContactError.TIMEOUT_ERROR = 3; 1649 | ContactError.PENDING_OPERATION_ERROR = 4; 1650 | ContactError.IO_ERROR = 5; 1651 | ContactError.NOT_SUPPORTED_ERROR = 6; 1652 | ContactError.PERMISSION_DENIED_ERROR = 20; 1653 | 1654 | /** 1655 | * Removes contact from device storage. 1656 | * @param successCB success callback 1657 | * @param errorCB error callback 1658 | */ 1659 | Contact.prototype.remove = function(successCB, errorCB) { 1660 | if (this.id === null) { 1661 | var errorObj = new ContactError(); 1662 | errorObj.code = ContactError.NOT_FOUND_ERROR; 1663 | errorCB(errorObj); 1664 | } 1665 | else { 1666 | PhoneGap.exec(successCB, errorCB, "Contacts", "remove", [this.id]); 1667 | } 1668 | }; 1669 | 1670 | /** 1671 | * Creates a deep copy of this Contact. 1672 | * With the contact ID set to null. 1673 | * @return copy of this Contact 1674 | */ 1675 | Contact.prototype.clone = function() { 1676 | var clonedContact = PhoneGap.clone(this); 1677 | var i; 1678 | clonedContact.id = null; 1679 | clonedContact.rawId = null; 1680 | // Loop through and clear out any id's in phones, emails, etc. 1681 | if (clonedContact.phoneNumbers) { 1682 | for (i = 0; i < clonedContact.phoneNumbers.length; i++) { 1683 | clonedContact.phoneNumbers[i].id = null; 1684 | } 1685 | } 1686 | if (clonedContact.emails) { 1687 | for (i = 0; i < clonedContact.emails.length; i++) { 1688 | clonedContact.emails[i].id = null; 1689 | } 1690 | } 1691 | if (clonedContact.addresses) { 1692 | for (i = 0; i < clonedContact.addresses.length; i++) { 1693 | clonedContact.addresses[i].id = null; 1694 | } 1695 | } 1696 | if (clonedContact.ims) { 1697 | for (i = 0; i < clonedContact.ims.length; i++) { 1698 | clonedContact.ims[i].id = null; 1699 | } 1700 | } 1701 | if (clonedContact.organizations) { 1702 | for (i = 0; i < clonedContact.organizations.length; i++) { 1703 | clonedContact.organizations[i].id = null; 1704 | } 1705 | } 1706 | if (clonedContact.tags) { 1707 | for (i = 0; i < clonedContact.tags.length; i++) { 1708 | clonedContact.tags[i].id = null; 1709 | } 1710 | } 1711 | if (clonedContact.photos) { 1712 | for (i = 0; i < clonedContact.photos.length; i++) { 1713 | clonedContact.photos[i].id = null; 1714 | } 1715 | } 1716 | if (clonedContact.urls) { 1717 | for (i = 0; i < clonedContact.urls.length; i++) { 1718 | clonedContact.urls[i].id = null; 1719 | } 1720 | } 1721 | return clonedContact; 1722 | }; 1723 | 1724 | /** 1725 | * Persists contact to device storage. 1726 | * @param successCB success callback 1727 | * @param errorCB error callback 1728 | */ 1729 | Contact.prototype.save = function(successCB, errorCB) { 1730 | PhoneGap.exec(successCB, errorCB, "Contacts", "save", [this]); 1731 | }; 1732 | 1733 | /** 1734 | * Contact name. 1735 | * @constructor 1736 | * @param formatted 1737 | * @param familyName 1738 | * @param givenName 1739 | * @param middle 1740 | * @param prefix 1741 | * @param suffix 1742 | */ 1743 | var ContactName = function(formatted, familyName, givenName, middle, prefix, suffix) { 1744 | this.formatted = formatted || null; 1745 | this.familyName = familyName || null; 1746 | this.givenName = givenName || null; 1747 | this.middleName = middle || null; 1748 | this.honorificPrefix = prefix || null; 1749 | this.honorificSuffix = suffix || null; 1750 | }; 1751 | 1752 | /** 1753 | * Generic contact field. 1754 | * @constructor 1755 | * @param {DOMString} id unique identifier, should only be set by native code 1756 | * @param type 1757 | * @param value 1758 | * @param pref 1759 | */ 1760 | var ContactField = function(type, value, pref) { 1761 | this.id = null; 1762 | this.type = type || null; 1763 | this.value = value || null; 1764 | this.pref = pref || null; 1765 | }; 1766 | 1767 | /** 1768 | * Contact address. 1769 | * @constructor 1770 | * @param {DOMString} id unique identifier, should only be set by native code 1771 | * @param formatted 1772 | * @param streetAddress 1773 | * @param locality 1774 | * @param region 1775 | * @param postalCode 1776 | * @param country 1777 | */ 1778 | var ContactAddress = function(formatted, streetAddress, locality, region, postalCode, country) { 1779 | this.id = null; 1780 | this.formatted = formatted || null; 1781 | this.streetAddress = streetAddress || null; 1782 | this.locality = locality || null; 1783 | this.region = region || null; 1784 | this.postalCode = postalCode || null; 1785 | this.country = country || null; 1786 | }; 1787 | 1788 | /** 1789 | * Contact organization. 1790 | * @constructor 1791 | * @param {DOMString} id unique identifier, should only be set by native code 1792 | * @param name 1793 | * @param dept 1794 | * @param title 1795 | * @param startDate 1796 | * @param endDate 1797 | * @param location 1798 | * @param desc 1799 | */ 1800 | var ContactOrganization = function(name, dept, title) { 1801 | this.id = null; 1802 | this.name = name || null; 1803 | this.department = dept || null; 1804 | this.title = title || null; 1805 | }; 1806 | 1807 | /** 1808 | * Represents a group of Contacts. 1809 | * @constructor 1810 | */ 1811 | var Contacts = function() { 1812 | this.inProgress = false; 1813 | this.records = []; 1814 | }; 1815 | /** 1816 | * Returns an array of Contacts matching the search criteria. 1817 | * @param fields that should be searched 1818 | * @param successCB success callback 1819 | * @param errorCB error callback 1820 | * @param {ContactFindOptions} options that can be applied to contact searching 1821 | * @return array of Contacts matching search criteria 1822 | */ 1823 | Contacts.prototype.find = function(fields, successCB, errorCB, options) { 1824 | PhoneGap.exec(successCB, errorCB, "Contacts", "search", [fields, options]); 1825 | }; 1826 | 1827 | /** 1828 | * This function creates a new contact, but it does not persist the contact 1829 | * to device storage. To persist the contact to device storage, invoke 1830 | * contact.save(). 1831 | * @param properties an object who's properties will be examined to create a new Contact 1832 | * @returns new Contact object 1833 | */ 1834 | Contacts.prototype.create = function(properties) { 1835 | var i; 1836 | var contact = new Contact(); 1837 | for (i in properties) { 1838 | if (contact[i] !== 'undefined') { 1839 | contact[i] = properties[i]; 1840 | } 1841 | } 1842 | return contact; 1843 | }; 1844 | 1845 | /** 1846 | * This function returns and array of contacts. It is required as we need to convert raw 1847 | * JSON objects into concrete Contact objects. Currently this method is called after 1848 | * navigator.service.contacts.find but before the find methods success call back. 1849 | * 1850 | * @param jsonArray an array of JSON Objects that need to be converted to Contact objects. 1851 | * @returns an array of Contact objects 1852 | */ 1853 | Contacts.prototype.cast = function(pluginResult) { 1854 | var contacts = []; 1855 | var i; 1856 | for (i=0; i][;base64], 2316 | * 2317 | * @param file {File} File object containing file properties 2318 | */ 2319 | FileReader.prototype.readAsDataURL = function(file) { 2320 | this.fileName = ""; 2321 | if (typeof file.fullPath === "undefined") { 2322 | this.fileName = file; 2323 | } else { 2324 | this.fileName = file.fullPath; 2325 | } 2326 | 2327 | // LOADING state 2328 | this.readyState = FileReader.LOADING; 2329 | 2330 | // If loadstart callback 2331 | if (typeof this.onloadstart === "function") { 2332 | this.onloadstart({"type":"loadstart", "target":this}); 2333 | } 2334 | 2335 | var me = this; 2336 | 2337 | // Read file 2338 | navigator.fileMgr.readAsDataURL(this.fileName, 2339 | 2340 | // Success callback 2341 | function(r) { 2342 | var evt; 2343 | 2344 | // If DONE (cancelled), then don't do anything 2345 | if (me.readyState === FileReader.DONE) { 2346 | return; 2347 | } 2348 | 2349 | // Save result 2350 | me.result = r; 2351 | 2352 | // If onload callback 2353 | if (typeof me.onload === "function") { 2354 | me.onload({"type":"load", "target":me}); 2355 | } 2356 | 2357 | // DONE state 2358 | me.readyState = FileReader.DONE; 2359 | 2360 | // If onloadend callback 2361 | if (typeof me.onloadend === "function") { 2362 | me.onloadend({"type":"loadend", "target":me}); 2363 | } 2364 | }, 2365 | 2366 | // Error callback 2367 | function(e) { 2368 | var evt; 2369 | // If DONE (cancelled), then don't do anything 2370 | if (me.readyState === FileReader.DONE) { 2371 | return; 2372 | } 2373 | 2374 | // Save error 2375 | me.error = e; 2376 | 2377 | // If onerror callback 2378 | if (typeof me.onerror === "function") { 2379 | me.onerror({"type":"error", "target":me}); 2380 | } 2381 | 2382 | // DONE state 2383 | me.readyState = FileReader.DONE; 2384 | 2385 | // If onloadend callback 2386 | if (typeof me.onloadend === "function") { 2387 | me.onloadend({"type":"loadend", "target":me}); 2388 | } 2389 | } 2390 | ); 2391 | }; 2392 | 2393 | /** 2394 | * Read file and return data as a binary data. 2395 | * 2396 | * @param file {File} File object containing file properties 2397 | */ 2398 | FileReader.prototype.readAsBinaryString = function(file) { 2399 | // TODO - Can't return binary data to browser. 2400 | this.fileName = file; 2401 | }; 2402 | 2403 | /** 2404 | * Read file and return data as a binary data. 2405 | * 2406 | * @param file {File} File object containing file properties 2407 | */ 2408 | FileReader.prototype.readAsArrayBuffer = function(file) { 2409 | // TODO - Can't return binary data to browser. 2410 | this.fileName = file; 2411 | }; 2412 | 2413 | //----------------------------------------------------------------------------- 2414 | // File Writer 2415 | //----------------------------------------------------------------------------- 2416 | 2417 | /** 2418 | * This class writes to the mobile device file system. 2419 | * 2420 | * For Android: 2421 | * The root directory is the root of the file system. 2422 | * To write to the SD card, the file name is "sdcard/my_file.txt" 2423 | * 2424 | * @constructor 2425 | * @param file {File} File object containing file properties 2426 | * @param append if true write to the end of the file, otherwise overwrite the file 2427 | */ 2428 | var FileWriter = function(file) { 2429 | this.fileName = ""; 2430 | this.length = 0; 2431 | if (file) { 2432 | this.fileName = file.fullPath || file; 2433 | this.length = file.size || 0; 2434 | } 2435 | // default is to write at the beginning of the file 2436 | this.position = 0; 2437 | 2438 | this.readyState = 0; // EMPTY 2439 | 2440 | this.result = null; 2441 | 2442 | // Error 2443 | this.error = null; 2444 | 2445 | // Event handlers 2446 | this.onwritestart = null; // When writing starts 2447 | this.onprogress = null; // While writing the file, and reporting partial file data 2448 | this.onwrite = null; // When the write has successfully completed. 2449 | this.onwriteend = null; // When the request has completed (either in success or failure). 2450 | this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method. 2451 | this.onerror = null; // When the write has failed (see errors). 2452 | }; 2453 | 2454 | // States 2455 | FileWriter.INIT = 0; 2456 | FileWriter.WRITING = 1; 2457 | FileWriter.DONE = 2; 2458 | 2459 | /** 2460 | * Abort writing file. 2461 | */ 2462 | FileWriter.prototype.abort = function() { 2463 | // check for invalid state 2464 | if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) { 2465 | throw FileError.INVALID_STATE_ERR; 2466 | } 2467 | 2468 | // set error 2469 | var error = new FileError(), evt; 2470 | error.code = error.ABORT_ERR; 2471 | this.error = error; 2472 | 2473 | // If error callback 2474 | if (typeof this.onerror === "function") { 2475 | this.onerror({"type":"error", "target":this}); 2476 | } 2477 | // If abort callback 2478 | if (typeof this.onabort === "function") { 2479 | this.oneabort({"type":"abort", "target":this}); 2480 | } 2481 | 2482 | this.readyState = FileWriter.DONE; 2483 | 2484 | // If write end callback 2485 | if (typeof this.onwriteend == "function") { 2486 | this.onwriteend({"type":"writeend", "target":this}); 2487 | } 2488 | }; 2489 | 2490 | /** 2491 | * Writes data to the file 2492 | * 2493 | * @param text to be written 2494 | */ 2495 | FileWriter.prototype.write = function(text) { 2496 | // Throw an exception if we are already writing a file 2497 | if (this.readyState === FileWriter.WRITING) { 2498 | throw FileError.INVALID_STATE_ERR; 2499 | } 2500 | 2501 | // WRITING state 2502 | this.readyState = FileWriter.WRITING; 2503 | 2504 | var me = this; 2505 | 2506 | // If onwritestart callback 2507 | if (typeof me.onwritestart === "function") { 2508 | me.onwritestart({"type":"writestart", "target":me}); 2509 | } 2510 | 2511 | // Write file 2512 | navigator.fileMgr.write(this.fileName, text, this.position, 2513 | 2514 | // Success callback 2515 | function(r) { 2516 | var evt; 2517 | // If DONE (cancelled), then don't do anything 2518 | if (me.readyState === FileWriter.DONE) { 2519 | return; 2520 | } 2521 | 2522 | // position always increases by bytes written because file would be extended 2523 | me.position += r; 2524 | // The length of the file is now where we are done writing. 2525 | me.length = me.position; 2526 | 2527 | // If onwrite callback 2528 | if (typeof me.onwrite === "function") { 2529 | me.onwrite({"type":"write", "target":me}); 2530 | } 2531 | 2532 | // DONE state 2533 | me.readyState = FileWriter.DONE; 2534 | 2535 | // If onwriteend callback 2536 | if (typeof me.onwriteend === "function") { 2537 | me.onwriteend({"type":"writeend", "target":me}); 2538 | } 2539 | }, 2540 | 2541 | // Error callback 2542 | function(e) { 2543 | var evt; 2544 | 2545 | // If DONE (cancelled), then don't do anything 2546 | if (me.readyState === FileWriter.DONE) { 2547 | return; 2548 | } 2549 | 2550 | // Save error 2551 | me.error = e; 2552 | 2553 | // If onerror callback 2554 | if (typeof me.onerror === "function") { 2555 | me.onerror({"type":"error", "target":me}); 2556 | } 2557 | 2558 | // DONE state 2559 | me.readyState = FileWriter.DONE; 2560 | 2561 | // If onwriteend callback 2562 | if (typeof me.onwriteend === "function") { 2563 | me.onwriteend({"type":"writeend", "target":me}); 2564 | } 2565 | } 2566 | ); 2567 | 2568 | }; 2569 | 2570 | /** 2571 | * Moves the file pointer to the location specified. 2572 | * 2573 | * If the offset is a negative number the position of the file 2574 | * pointer is rewound. If the offset is greater than the file 2575 | * size the position is set to the end of the file. 2576 | * 2577 | * @param offset is the location to move the file pointer to. 2578 | */ 2579 | FileWriter.prototype.seek = function(offset) { 2580 | // Throw an exception if we are already writing a file 2581 | if (this.readyState === FileWriter.WRITING) { 2582 | throw FileError.INVALID_STATE_ERR; 2583 | } 2584 | 2585 | if (!offset) { 2586 | return; 2587 | } 2588 | 2589 | // See back from end of file. 2590 | if (offset < 0) { 2591 | this.position = Math.max(offset + this.length, 0); 2592 | } 2593 | // Offset is bigger then file size so set position 2594 | // to the end of the file. 2595 | else if (offset > this.length) { 2596 | this.position = this.length; 2597 | } 2598 | // Offset is between 0 and file size so set the position 2599 | // to start writing. 2600 | else { 2601 | this.position = offset; 2602 | } 2603 | }; 2604 | 2605 | /** 2606 | * Truncates the file to the size specified. 2607 | * 2608 | * @param size to chop the file at. 2609 | */ 2610 | FileWriter.prototype.truncate = function(size) { 2611 | // Throw an exception if we are already writing a file 2612 | if (this.readyState === FileWriter.WRITING) { 2613 | throw FileError.INVALID_STATE_ERR; 2614 | } 2615 | 2616 | // WRITING state 2617 | this.readyState = FileWriter.WRITING; 2618 | 2619 | var me = this; 2620 | 2621 | // If onwritestart callback 2622 | if (typeof me.onwritestart === "function") { 2623 | me.onwritestart({"type":"writestart", "target":this}); 2624 | } 2625 | 2626 | // Write file 2627 | navigator.fileMgr.truncate(this.fileName, size, 2628 | 2629 | // Success callback 2630 | function(r) { 2631 | var evt; 2632 | // If DONE (cancelled), then don't do anything 2633 | if (me.readyState === FileWriter.DONE) { 2634 | return; 2635 | } 2636 | 2637 | // Update the length of the file 2638 | me.length = r; 2639 | me.position = Math.min(me.position, r); 2640 | 2641 | // If onwrite callback 2642 | if (typeof me.onwrite === "function") { 2643 | me.onwrite({"type":"write", "target":me}); 2644 | } 2645 | 2646 | // DONE state 2647 | me.readyState = FileWriter.DONE; 2648 | 2649 | // If onwriteend callback 2650 | if (typeof me.onwriteend === "function") { 2651 | me.onwriteend({"type":"writeend", "target":me}); 2652 | } 2653 | }, 2654 | 2655 | // Error callback 2656 | function(e) { 2657 | var evt; 2658 | // If DONE (cancelled), then don't do anything 2659 | if (me.readyState === FileWriter.DONE) { 2660 | return; 2661 | } 2662 | 2663 | // Save error 2664 | me.error = e; 2665 | 2666 | // If onerror callback 2667 | if (typeof me.onerror === "function") { 2668 | me.onerror({"type":"error", "target":me}); 2669 | } 2670 | 2671 | // DONE state 2672 | me.readyState = FileWriter.DONE; 2673 | 2674 | // If onwriteend callback 2675 | if (typeof me.onwriteend === "function") { 2676 | me.onwriteend({"type":"writeend", "target":me}); 2677 | } 2678 | } 2679 | ); 2680 | }; 2681 | 2682 | /** 2683 | * Information about the state of the file or directory 2684 | * 2685 | * @constructor 2686 | * {Date} modificationTime (readonly) 2687 | */ 2688 | var Metadata = function() { 2689 | this.modificationTime=null; 2690 | }; 2691 | 2692 | /** 2693 | * Supplies arguments to methods that lookup or create files and directories 2694 | * 2695 | * @constructor 2696 | * @param {boolean} create file or directory if it doesn't exist 2697 | * @param {boolean} exclusive if true the command will fail if the file or directory exists 2698 | */ 2699 | var Flags = function(create, exclusive) { 2700 | this.create = create || false; 2701 | this.exclusive = exclusive || false; 2702 | }; 2703 | 2704 | /** 2705 | * An interface representing a file system 2706 | * 2707 | * @constructor 2708 | * {DOMString} name the unique name of the file system (readonly) 2709 | * {DirectoryEntry} root directory of the file system (readonly) 2710 | */ 2711 | var FileSystem = function() { 2712 | this.name = null; 2713 | this.root = null; 2714 | }; 2715 | 2716 | /** 2717 | * An interface that lists the files and directories in a directory. 2718 | * @constructor 2719 | */ 2720 | var DirectoryReader = function(fullPath){ 2721 | this.fullPath = fullPath || null; 2722 | }; 2723 | 2724 | /** 2725 | * Returns a list of entries from a directory. 2726 | * 2727 | * @param {Function} successCallback is called with a list of entries 2728 | * @param {Function} errorCallback is called with a FileError 2729 | */ 2730 | DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) { 2731 | PhoneGap.exec(successCallback, errorCallback, "File", "readEntries", [this.fullPath]); 2732 | }; 2733 | 2734 | /** 2735 | * An interface representing a directory on the file system. 2736 | * 2737 | * @constructor 2738 | * {boolean} isFile always false (readonly) 2739 | * {boolean} isDirectory always true (readonly) 2740 | * {DOMString} name of the directory, excluding the path leading to it (readonly) 2741 | * {DOMString} fullPath the absolute full path to the directory (readonly) 2742 | * {FileSystem} filesystem on which the directory resides (readonly) 2743 | */ 2744 | var DirectoryEntry = function() { 2745 | this.isFile = false; 2746 | this.isDirectory = true; 2747 | this.name = null; 2748 | this.fullPath = null; 2749 | this.filesystem = null; 2750 | }; 2751 | 2752 | /** 2753 | * Copies a directory to a new location 2754 | * 2755 | * @param {DirectoryEntry} parent the directory to which to copy the entry 2756 | * @param {DOMString} newName the new name of the entry, defaults to the current name 2757 | * @param {Function} successCallback is called with the new entry 2758 | * @param {Function} errorCallback is called with a FileError 2759 | */ 2760 | DirectoryEntry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) { 2761 | PhoneGap.exec(successCallback, errorCallback, "File", "copyTo", [this.fullPath, parent, newName]); 2762 | }; 2763 | 2764 | /** 2765 | * Looks up the metadata of the entry 2766 | * 2767 | * @param {Function} successCallback is called with a Metadata object 2768 | * @param {Function} errorCallback is called with a FileError 2769 | */ 2770 | DirectoryEntry.prototype.getMetadata = function(successCallback, errorCallback) { 2771 | PhoneGap.exec(successCallback, errorCallback, "File", "getMetadata", [this.fullPath]); 2772 | }; 2773 | 2774 | /** 2775 | * Gets the parent of the entry 2776 | * 2777 | * @param {Function} successCallback is called with a parent entry 2778 | * @param {Function} errorCallback is called with a FileError 2779 | */ 2780 | DirectoryEntry.prototype.getParent = function(successCallback, errorCallback) { 2781 | PhoneGap.exec(successCallback, errorCallback, "File", "getParent", [this.fullPath]); 2782 | }; 2783 | 2784 | /** 2785 | * Moves a directory to a new location 2786 | * 2787 | * @param {DirectoryEntry} parent the directory to which to move the entry 2788 | * @param {DOMString} newName the new name of the entry, defaults to the current name 2789 | * @param {Function} successCallback is called with the new entry 2790 | * @param {Function} errorCallback is called with a FileError 2791 | */ 2792 | DirectoryEntry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) { 2793 | PhoneGap.exec(successCallback, errorCallback, "File", "moveTo", [this.fullPath, parent, newName]); 2794 | }; 2795 | 2796 | /** 2797 | * Removes the entry 2798 | * 2799 | * @param {Function} successCallback is called with no parameters 2800 | * @param {Function} errorCallback is called with a FileError 2801 | */ 2802 | DirectoryEntry.prototype.remove = function(successCallback, errorCallback) { 2803 | PhoneGap.exec(successCallback, errorCallback, "File", "remove", [this.fullPath]); 2804 | }; 2805 | 2806 | /** 2807 | * Returns a URI that can be used to identify this entry. 2808 | * 2809 | * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI. 2810 | * @return uri 2811 | */ 2812 | DirectoryEntry.prototype.toURI = function(mimeType) { 2813 | return "file://" + this.fullPath; 2814 | }; 2815 | 2816 | /** 2817 | * Creates a new DirectoryReader to read entries from this directory 2818 | */ 2819 | DirectoryEntry.prototype.createReader = function(successCallback, errorCallback) { 2820 | return new DirectoryReader(this.fullPath); 2821 | }; 2822 | 2823 | /** 2824 | * Creates or looks up a directory 2825 | * 2826 | * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory 2827 | * @param {Flags} options to create or excluively create the directory 2828 | * @param {Function} successCallback is called with the new entry 2829 | * @param {Function} errorCallback is called with a FileError 2830 | */ 2831 | DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) { 2832 | PhoneGap.exec(successCallback, errorCallback, "File", "getDirectory", [this.fullPath, path, options]); 2833 | }; 2834 | 2835 | /** 2836 | * Creates or looks up a file 2837 | * 2838 | * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file 2839 | * @param {Flags} options to create or excluively create the file 2840 | * @param {Function} successCallback is called with the new entry 2841 | * @param {Function} errorCallback is called with a FileError 2842 | */ 2843 | DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) { 2844 | PhoneGap.exec(successCallback, errorCallback, "File", "getFile", [this.fullPath, path, options]); 2845 | }; 2846 | 2847 | /** 2848 | * Deletes a directory and all of it's contents 2849 | * 2850 | * @param {Function} successCallback is called with no parameters 2851 | * @param {Function} errorCallback is called with a FileError 2852 | */ 2853 | DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) { 2854 | PhoneGap.exec(successCallback, errorCallback, "File", "removeRecursively", [this.fullPath]); 2855 | }; 2856 | 2857 | /** 2858 | * An interface representing a directory on the file system. 2859 | * 2860 | * @constructor 2861 | * {boolean} isFile always true (readonly) 2862 | * {boolean} isDirectory always false (readonly) 2863 | * {DOMString} name of the file, excluding the path leading to it (readonly) 2864 | * {DOMString} fullPath the absolute full path to the file (readonly) 2865 | * {FileSystem} filesystem on which the directory resides (readonly) 2866 | */ 2867 | var FileEntry = function() { 2868 | this.isFile = true; 2869 | this.isDirectory = false; 2870 | this.name = null; 2871 | this.fullPath = null; 2872 | this.filesystem = null; 2873 | }; 2874 | 2875 | /** 2876 | * Copies a file to a new location 2877 | * 2878 | * @param {DirectoryEntry} parent the directory to which to copy the entry 2879 | * @param {DOMString} newName the new name of the entry, defaults to the current name 2880 | * @param {Function} successCallback is called with the new entry 2881 | * @param {Function} errorCallback is called with a FileError 2882 | */ 2883 | FileEntry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) { 2884 | PhoneGap.exec(successCallback, errorCallback, "File", "copyTo", [this.fullPath, parent, newName]); 2885 | }; 2886 | 2887 | /** 2888 | * Looks up the metadata of the entry 2889 | * 2890 | * @param {Function} successCallback is called with a Metadata object 2891 | * @param {Function} errorCallback is called with a FileError 2892 | */ 2893 | FileEntry.prototype.getMetadata = function(successCallback, errorCallback) { 2894 | PhoneGap.exec(successCallback, errorCallback, "File", "getMetadata", [this.fullPath]); 2895 | }; 2896 | 2897 | /** 2898 | * Gets the parent of the entry 2899 | * 2900 | * @param {Function} successCallback is called with a parent entry 2901 | * @param {Function} errorCallback is called with a FileError 2902 | */ 2903 | FileEntry.prototype.getParent = function(successCallback, errorCallback) { 2904 | PhoneGap.exec(successCallback, errorCallback, "File", "getParent", [this.fullPath]); 2905 | }; 2906 | 2907 | /** 2908 | * Moves a directory to a new location 2909 | * 2910 | * @param {DirectoryEntry} parent the directory to which to move the entry 2911 | * @param {DOMString} newName the new name of the entry, defaults to the current name 2912 | * @param {Function} successCallback is called with the new entry 2913 | * @param {Function} errorCallback is called with a FileError 2914 | */ 2915 | FileEntry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) { 2916 | PhoneGap.exec(successCallback, errorCallback, "File", "moveTo", [this.fullPath, parent, newName]); 2917 | }; 2918 | 2919 | /** 2920 | * Removes the entry 2921 | * 2922 | * @param {Function} successCallback is called with no parameters 2923 | * @param {Function} errorCallback is called with a FileError 2924 | */ 2925 | FileEntry.prototype.remove = function(successCallback, errorCallback) { 2926 | PhoneGap.exec(successCallback, errorCallback, "File", "remove", [this.fullPath]); 2927 | }; 2928 | 2929 | /** 2930 | * Returns a URI that can be used to identify this entry. 2931 | * 2932 | * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI. 2933 | * @return uri 2934 | */ 2935 | FileEntry.prototype.toURI = function(mimeType) { 2936 | return "file://" + this.fullPath; 2937 | }; 2938 | 2939 | /** 2940 | * Creates a new FileWriter associated with the file that this FileEntry represents. 2941 | * 2942 | * @param {Function} successCallback is called with the new FileWriter 2943 | * @param {Function} errorCallback is called with a FileError 2944 | */ 2945 | FileEntry.prototype.createWriter = function(successCallback, errorCallback) { 2946 | this.file(function(filePointer) { 2947 | var writer = new FileWriter(filePointer); 2948 | 2949 | if (writer.fileName === null || writer.fileName === "") { 2950 | if (typeof errorCallback == "function") { 2951 | errorCallback({ 2952 | "code": FileError.INVALID_STATE_ERR 2953 | }); 2954 | } 2955 | } 2956 | 2957 | if (typeof successCallback == "function") { 2958 | successCallback(writer); 2959 | } 2960 | }, errorCallback); 2961 | }; 2962 | 2963 | /** 2964 | * Returns a File that represents the current state of the file that this FileEntry represents. 2965 | * 2966 | * @param {Function} successCallback is called with the new File object 2967 | * @param {Function} errorCallback is called with a FileError 2968 | */ 2969 | FileEntry.prototype.file = function(successCallback, errorCallback) { 2970 | PhoneGap.exec(successCallback, errorCallback, "File", "getFileMetadata", [this.fullPath]); 2971 | }; 2972 | 2973 | /** @constructor */ 2974 | var LocalFileSystem = function() { 2975 | }; 2976 | 2977 | // File error codes 2978 | LocalFileSystem.TEMPORARY = 0; 2979 | LocalFileSystem.PERSISTENT = 1; 2980 | LocalFileSystem.RESOURCE = 2; 2981 | LocalFileSystem.APPLICATION = 3; 2982 | 2983 | /** 2984 | * Requests a filesystem in which to store application data. 2985 | * 2986 | * @param {int} type of file system being requested 2987 | * @param {Function} successCallback is called with the new FileSystem 2988 | * @param {Function} errorCallback is called with a FileError 2989 | */ 2990 | LocalFileSystem.prototype.requestFileSystem = function(type, size, successCallback, errorCallback) { 2991 | if (type < 0 || type > 3) { 2992 | if (typeof errorCallback == "function") { 2993 | errorCallback({ 2994 | "code": FileError.SYNTAX_ERR 2995 | }); 2996 | } 2997 | } 2998 | else { 2999 | PhoneGap.exec(successCallback, errorCallback, "File", "requestFileSystem", [type, size]); 3000 | } 3001 | }; 3002 | 3003 | /** 3004 | * 3005 | * @param {DOMString} uri referring to a local file in a filesystem 3006 | * @param {Function} successCallback is called with the new entry 3007 | * @param {Function} errorCallback is called with a FileError 3008 | */ 3009 | LocalFileSystem.prototype.resolveLocalFileSystemURI = function(uri, successCallback, errorCallback) { 3010 | PhoneGap.exec(successCallback, errorCallback, "File", "resolveLocalFileSystemURI", [uri]); 3011 | }; 3012 | 3013 | /** 3014 | * This function returns and array of contacts. It is required as we need to convert raw 3015 | * JSON objects into concrete Contact objects. Currently this method is called after 3016 | * navigator.service.contacts.find but before the find methods success call back. 3017 | * 3018 | * @param a JSON Objects that need to be converted to DirectoryEntry or FileEntry objects. 3019 | * @returns an entry 3020 | */ 3021 | LocalFileSystem.prototype._castFS = function(pluginResult) { 3022 | var entry = null; 3023 | entry = new DirectoryEntry(); 3024 | entry.isDirectory = pluginResult.message.root.isDirectory; 3025 | entry.isFile = pluginResult.message.root.isFile; 3026 | entry.name = pluginResult.message.root.name; 3027 | entry.fullPath = pluginResult.message.root.fullPath; 3028 | pluginResult.message.root = entry; 3029 | return pluginResult; 3030 | }; 3031 | 3032 | LocalFileSystem.prototype._castEntry = function(pluginResult) { 3033 | var entry = null; 3034 | if (pluginResult.message.isDirectory) { 3035 | console.log("This is a dir"); 3036 | entry = new DirectoryEntry(); 3037 | } 3038 | else if (pluginResult.message.isFile) { 3039 | console.log("This is a file"); 3040 | entry = new FileEntry(); 3041 | } 3042 | entry.isDirectory = pluginResult.message.isDirectory; 3043 | entry.isFile = pluginResult.message.isFile; 3044 | entry.name = pluginResult.message.name; 3045 | entry.fullPath = pluginResult.message.fullPath; 3046 | pluginResult.message = entry; 3047 | return pluginResult; 3048 | }; 3049 | 3050 | LocalFileSystem.prototype._castEntries = function(pluginResult) { 3051 | var entries = pluginResult.message; 3052 | var retVal = []; 3053 | for (var i=0; i