├── 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 |
66 |
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