mirror of
https://github.com/apache/cordova-android.git
synced 2025-03-18 18:07:06 +08:00

Contact search was not working for unicode letters. The CallbackServer was changed so that it returned url encode strings. On the JavaScript side the PhoneGap callback handler decodes the returned string.
325 lines
8.4 KiB
Java
Executable File
325 lines
8.4 KiB
Java
Executable File
/*
|
|
* PhoneGap is available under *either* the terms of the modified BSD license *or* the
|
|
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
|
|
*
|
|
* Copyright (c) 2005-2010, Nitobi Software Inc.
|
|
* Copyright (c) 2010, IBM Corporation
|
|
*/
|
|
package com.phonegap;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.DataOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStreamReader;
|
|
import java.net.ServerSocket;
|
|
import java.net.Socket;
|
|
import java.net.URLEncoder;
|
|
import java.util.LinkedList;
|
|
|
|
/**
|
|
* This class provides a way for Java to run JavaScript in the web page that has loaded PhoneGap.
|
|
* The CallbackServer class implements an XHR server and a polling server with a list of JavaScript
|
|
* statements that are to be executed on the web page.
|
|
*
|
|
* The process flow for XHR is:
|
|
* 1. JavaScript makes an async XHR call.
|
|
* 2. The server holds the connection open until data is available.
|
|
* 3. The server writes the data to the client and closes the connection.
|
|
* 4. The server immediately starts listening for the next XHR call.
|
|
* 5. The client receives this XHR response, processes it.
|
|
* 6. The client sends a new async XHR request.
|
|
*
|
|
* The CallbackServer class requires the following permission in Android manifest file
|
|
* <uses-permission android:name="android.permission.INTERNET" />
|
|
*
|
|
* If the device has a proxy set, then XHR cannot be used, so polling must be used instead.
|
|
* This can be determined by the client by calling CallbackServer.usePolling().
|
|
*
|
|
* The process flow for polling is:
|
|
* 1. The client calls CallbackServer.getJavascript() to retrieve next statement.
|
|
* 2. If statement available, then client processes it.
|
|
* 3. The client repeats #1 in loop.
|
|
*/
|
|
public class CallbackServer implements Runnable {
|
|
|
|
/**
|
|
* The list of JavaScript statements to be sent to JavaScript.
|
|
*/
|
|
private LinkedList<String> javascript;
|
|
|
|
/**
|
|
* The port to listen on.
|
|
*/
|
|
private int port;
|
|
|
|
/**
|
|
* The server thread.
|
|
*/
|
|
private Thread serverThread;
|
|
|
|
/**
|
|
* Indicates the server is running.
|
|
*/
|
|
private boolean active;
|
|
|
|
/**
|
|
* Indicates that the JavaScript statements list is empty
|
|
*/
|
|
private boolean empty;
|
|
|
|
/**
|
|
* Indicates that polling should be used instead of XHR.
|
|
*/
|
|
private boolean usePolling = true;
|
|
|
|
/**
|
|
* Security token to prevent other apps from accessing this callback server via XHR
|
|
*/
|
|
private String token;
|
|
|
|
/**
|
|
* Constructor.
|
|
*/
|
|
public CallbackServer() {
|
|
//System.out.println("CallbackServer()");
|
|
this.active = false;
|
|
this.empty = true;
|
|
this.port = 0;
|
|
this.javascript = new LinkedList<String>();
|
|
}
|
|
|
|
/**
|
|
* Init callback server and start XHR if running local app.
|
|
*
|
|
* If PhoneGap app is loaded from file://, then we can use XHR
|
|
* otherwise we have to use polling due to cross-domain security restrictions.
|
|
*
|
|
* @param url The URL of the PhoneGap app being loaded
|
|
*/
|
|
public void init(String url) {
|
|
//System.out.println("CallbackServer.start("+url+")");
|
|
|
|
// Determine if XHR or polling is to be used
|
|
if ((url != null) && !url.startsWith("file://")) {
|
|
this.usePolling = true;
|
|
this.stopServer();
|
|
}
|
|
else if (android.net.Proxy.getDefaultHost() != null) {
|
|
this.usePolling = true;
|
|
this.stopServer();
|
|
}
|
|
else {
|
|
this.usePolling = false;
|
|
this.startServer();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return if polling is being used instead of XHR.
|
|
*
|
|
* @return
|
|
*/
|
|
public boolean usePolling() {
|
|
return this.usePolling;
|
|
}
|
|
|
|
/**
|
|
* Get the port that this server is running on.
|
|
*
|
|
* @return
|
|
*/
|
|
public int getPort() {
|
|
return this.port;
|
|
}
|
|
|
|
/**
|
|
* Get the security token that this server requires when calling getJavascript().
|
|
*
|
|
* @return
|
|
*/
|
|
public String getToken() {
|
|
return this.token;
|
|
}
|
|
|
|
/**
|
|
* Start the server on a new thread.
|
|
*/
|
|
public void startServer() {
|
|
//System.out.println("CallbackServer.startServer()");
|
|
this.active = false;
|
|
|
|
// Start server on new thread
|
|
this.serverThread = new Thread(this);
|
|
this.serverThread.start();
|
|
}
|
|
|
|
/**
|
|
* Restart the server on a new thread.
|
|
*/
|
|
public void restartServer() {
|
|
|
|
// Stop server
|
|
this.stopServer();
|
|
|
|
// Start server again
|
|
this.startServer();
|
|
}
|
|
|
|
/**
|
|
* Start running the server.
|
|
* This is called automatically when the server thread is started.
|
|
*/
|
|
public void run() {
|
|
|
|
// Start server
|
|
try {
|
|
this.active = true;
|
|
String request;
|
|
ServerSocket waitSocket = new ServerSocket(0);
|
|
this.port = waitSocket.getLocalPort();
|
|
//System.out.println("CallbackServer -- using port " +this.port);
|
|
this.token = java.util.UUID.randomUUID().toString();
|
|
//System.out.println("CallbackServer -- using token "+this.token);
|
|
|
|
while (this.active) {
|
|
//System.out.println("CallbackServer: Waiting for data on socket");
|
|
Socket connection = waitSocket.accept();
|
|
BufferedReader xhrReader = new BufferedReader(new InputStreamReader(connection.getInputStream()),40);
|
|
DataOutputStream output = new DataOutputStream(connection.getOutputStream());
|
|
request = xhrReader.readLine();
|
|
String response = "";
|
|
//System.out.println("CallbackServerRequest="+request);
|
|
if (this.active && (request != null)) {
|
|
if (request.contains("GET")) {
|
|
|
|
// Get requested file
|
|
String[] requestParts = request.split(" ");
|
|
|
|
// Must have security token
|
|
if ((requestParts.length == 3) && (requestParts[1].substring(1).equals(this.token))) {
|
|
//System.out.println("CallbackServer -- Processing GET request");
|
|
|
|
// Wait until there is some data to send, or send empty data every 10 sec
|
|
// to prevent XHR timeout on the client
|
|
synchronized (this) {
|
|
while (this.empty) {
|
|
try {
|
|
this.wait(10000); // prevent timeout from happening
|
|
//System.out.println("CallbackServer>>> break <<<");
|
|
break;
|
|
}
|
|
catch (Exception e) { }
|
|
}
|
|
}
|
|
|
|
// If server is still running
|
|
if (this.active) {
|
|
|
|
// If no data, then send 404 back to client before it times out
|
|
if (this.empty) {
|
|
//System.out.println("CallbackServer -- sending data 0");
|
|
response = "HTTP/1.1 404 NO DATA\r\n\r\n "; // need to send content otherwise some Android devices fail, so send space
|
|
}
|
|
else {
|
|
//System.out.println("CallbackServer -- sending item");
|
|
response = "HTTP/1.1 200 OK\r\n\r\n";
|
|
String js = this.getJavascript();
|
|
if (js != null)
|
|
response += URLEncoder.encode(js, "UTF-8");
|
|
}
|
|
}
|
|
else {
|
|
response = "HTTP/1.1 503 Service Unavailable\r\n\r\n ";
|
|
}
|
|
}
|
|
else {
|
|
response = "HTTP/1.1 403 Forbidden\r\n\r\n ";
|
|
}
|
|
}
|
|
else {
|
|
response = "HTTP/1.1 400 Bad Request\r\n\r\n ";
|
|
}
|
|
//System.out.println("CallbackServer: response="+response);
|
|
//System.out.println("CallbackServer: closing output");
|
|
output.writeBytes(response);
|
|
output.flush();
|
|
}
|
|
output.close();
|
|
xhrReader.close();
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
this.active = false;
|
|
//System.out.println("CallbackServer.startServer() - EXIT");
|
|
}
|
|
|
|
/**
|
|
* Stop server.
|
|
* This stops the thread that the server is running on.
|
|
*/
|
|
public void stopServer() {
|
|
//System.out.println("CallbackServer.stopServer()");
|
|
if (this.active) {
|
|
this.active = false;
|
|
|
|
// Break out of server wait
|
|
synchronized (this) {
|
|
this.notify();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Destroy
|
|
*/
|
|
public void destroy() {
|
|
this.stopServer();
|
|
}
|
|
|
|
/**
|
|
* Get the number of JavaScript statements.
|
|
*
|
|
* @return int
|
|
*/
|
|
public int getSize() {
|
|
int size = this.javascript.size();
|
|
//System.out.println("getSize() = " + size);
|
|
return size;
|
|
}
|
|
|
|
/**
|
|
* Get the next JavaScript statement and remove from list.
|
|
*
|
|
* @return String
|
|
*/
|
|
public String getJavascript() {
|
|
if (this.javascript.size() == 0) {
|
|
return null;
|
|
}
|
|
String statement = this.javascript.remove(0);
|
|
//System.out.println("CallbackServer.getJavascript() = " + statement);
|
|
if (this.javascript.size() == 0) {
|
|
synchronized (this) {
|
|
this.empty = true;
|
|
}
|
|
}
|
|
return statement;
|
|
}
|
|
|
|
/**
|
|
* Add a JavaScript statement to the list.
|
|
*
|
|
* @param statement
|
|
*/
|
|
public void sendJavascript(String statement) {
|
|
//System.out.println("CallbackServer.sendJavascript("+statement+")");
|
|
this.javascript.add(statement);
|
|
synchronized (this) {
|
|
this.empty = false;
|
|
this.notify();
|
|
}
|
|
}
|
|
|
|
}
|