refactored the Command stuff a bit more, added a spashscreen

This commit is contained in:
Dave Johnson
2010-07-30 12:23:55 -07:00
parent 1af469c8b1
commit 49de553ee8
13 changed files with 277 additions and 272 deletions

View File

@@ -38,6 +38,7 @@ import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
@@ -53,6 +54,7 @@ import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.webkit.GeolocationPermissions.Callback;
import android.webkit.WebSettings.LayoutAlgorithm;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.os.Build.*;
import android.provider.MediaStore;
@@ -61,6 +63,7 @@ public class DroidGap extends Activity {
private static final String LOG_TAG = "DroidGap";
protected WebView appView;
protected ImageView splashScreen;
private LinearLayout root;
private Device gap;
@@ -77,39 +80,53 @@ public class DroidGap extends Activity {
private AudioHandler audio;
private CallbackServer callbackServer;
private CommandManager commandManager;
private Uri imageUri;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().requestFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
// This builds the view. We could probably get away with NOT having a LinearLayout, but I like having a bucket!
LinearLayout.LayoutParams containerParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT, 0.0F);
LinearLayout.LayoutParams webviewParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT, 1.0F);
root = new LinearLayout(this);
root.setOrientation(LinearLayout.VERTICAL);
root.setBackgroundColor(Color.BLACK);
root.setLayoutParams(containerParams);
appView = new WebView(this);
appView.setLayoutParams(webviewParams);
root.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT, 0.0F));
splashScreen = new ImageView(this);
splashScreen.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT,
1.0F));
splashScreen.setImageResource(R.drawable.splash);
root.addView(splashScreen);
initWebView();
setContentView(root);
}
private void initWebView() {
appView = new WebView(DroidGap.this);
appView.setLayoutParams(new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT,
ViewGroup.LayoutParams.FILL_PARENT,
1.0F));
WebViewReflect.checkCompatibility();
if (android.os.Build.VERSION.RELEASE.startsWith("2."))
appView.setWebChromeClient(new EclairClient(this));
else
{
appView.setWebChromeClient(new GapClient(this));
if (android.os.Build.VERSION.RELEASE.startsWith("2.")) {
appView.setWebChromeClient(new EclairClient(DroidGap.this));
} else {
appView.setWebChromeClient(new GapClient(DroidGap.this));
}
appView.setWebViewClient(new GapViewClient(this));
@@ -131,23 +148,15 @@ public class DroidGap extends Activity {
WebViewReflect.setDomStorage(settings);
// Turn off native geolocation object in browser - we use our own :)
WebViewReflect.setGeolocationEnabled(settings, true);
/* Bind the appView object to the gap class methods */
// Bind the appView object to the gap class methods
bindBrowser(appView);
if(cupcakeStorage != null)
cupcakeStorage.setStorage(appPackage);
root.addView(appView);
// Try firing the onNativeReady event in JS. If it fails because the JS is
// not loaded yet then just set a flag so that the onNativeReady can be fired
// from the JS side when the JS gets to that code.
appView.loadUrl("javascript:try{PhoneGap.onNativeReady.fire();}catch(e){_nativeReady = true;}");
setContentView(root);
}
public void invoke(String origin, boolean allow, boolean remember) {
}
@Override
@@ -260,6 +269,7 @@ public class DroidGap extends Activity {
appView.addJavascriptInterface(mKey, "BackButton");
appView.addJavascriptInterface(audio, "GapAudio");
appView.addJavascriptInterface(callbackServer, "CallbackServer");
//appView.addJavascriptInterface(new SplashScreen(this), "SplashScreen");
if (android.os.Build.VERSION.RELEASE.startsWith("1."))
{
@@ -272,7 +282,7 @@ public class DroidGap extends Activity {
public void loadUrl(String url)
{
appView.loadUrl(url);
this.appView.loadUrl(url);
}
/**
@@ -411,33 +421,39 @@ public class DroidGap extends Activity {
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (mKey.isBound())
{
//We fire an event here!
appView.loadUrl("javascript:document.keyEvent.backTrigger()");
}
else
{
String testUrl = appView.getUrl();
appView.goBack();
if(appView.getUrl().equals(testUrl))
{
return super.onKeyDown(keyCode, event);
}
}
}
if (keyCode == KeyEvent.KEYCODE_MENU)
{
// This is where we launch the menu
appView.loadUrl("javascript:keyEvent.menuTrigger()");
}
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (mKey.isBound())
{
//We fire an event here!
appView.loadUrl("javascript:document.keyEvent.backTrigger()");
}
else
{
String testUrl = appView.getUrl();
appView.goBack();
if(appView.getUrl().equals(testUrl))
{
return super.onKeyDown(keyCode, event);
}
}
}
if (keyCode == KeyEvent.KEYCODE_MENU)
{
// This is where we launch the menu
appView.loadUrl("javascript:keyEvent.menuTrigger()");
}
return false;
}
/**
* Removes the splash screen from root view and adds the WebView
*/
public void hideSplashScreen() {
root.removeView(splashScreen);
root.addView(appView);
}
// This is required to start the camera activity! It has to come from the previous activity
public void startCamera()
{
@@ -469,11 +485,5 @@ public class DroidGap extends Activity {
{
launcher.failPicture("Did not complete!");
}
}
public WebView getView()
{
return this.appView;
}
}
}

View File

@@ -0,0 +1,15 @@
package com.phonegap;
public class SplashScreen {
private final DroidGap gap;
public SplashScreen(DroidGap gap) {
this.gap = gap;
}
public void hide() {
gap.runOnUiThread(new Runnable() {
public void run() {
gap.hideSplashScreen();
}
});
}
}

View File

@@ -1,24 +1,41 @@
package com.phonegap.api;
import android.content.Context;
import org.json.JSONArray;
import android.content.Context;
import android.webkit.WebView;
/**
* Command interface must be implemented by any plugin classes.
*
* The execute method is called by the CommandManager.
*
* @author davejohnson
*
*/
public interface Command {
/**
* Executes the request and returns JS code to change client state.
*
* @param action the command to execute
* @return a string with JavaScript code or null
* Executes the request and returns CommandResult.
*
* @param action The command to execute.
* @param args JSONArry of arguments for the command.
* @return A CommandResult object with a status and message.
*/
CommandResult execute(String action, String[] args);
CommandResult execute(String action, JSONArray args);
/**
* Determines if this command can process a request.
*
* @param action the command to execute
*
* @return true if this command understands the petition
* Sets the context of the Command. This can then be used to do things like
* get file paths associated with the Activity.
*
* @param ctx The context of the main Activity.
*/
boolean accept(String action);
void setContext(Context ctx);
/**
* Sets the main View of the application, this is the WebView within which
* a PhoneGap app runs.
*
* @param webView The PhoneGap WebView
*/
void setView(WebView webView);
}

View File

@@ -1,12 +1,21 @@
package com.phonegap.api;
import org.json.JSONArray;
import org.json.JSONException;
import android.content.Context;
import android.webkit.WebView;
import com.phonegap.DroidGap;
/**
* Given a execution request detects matching {@link Command} and executes it.
* CommandManager is exposed to JavaScript in the PhoneGap WebView.
*
* Calling native plugin code can be done by calling CommandManager.exec(...)
* from JavaScript.
*
* @author davejohnson
*
*/
public final class CommandManager {
private static final String EXCEPTION_PREFIX = "[PhoneGap] *ERROR* Exception executing command [";
@@ -23,42 +32,57 @@ public final class CommandManager {
}
/**
* Receives a request for execution and fulfills it as long as one of
* the configured {@link Command} can understand it. Command precedence
* is important (just one of them will be executed).
*
* @param instruction any API command
* @return JS code to execute by the client or null
* Receives a request for execution and fulfills it by finding the appropriate
* Java class and calling it's execute method.
*
* CommandManager.exec can be used either synchronously or async. In either case, a JSON encoded
* string is returned that will indicate if any errors have occurred when trying to find
* or execute the class denoted by the clazz argument.
*
* @param clazz String containing the fully qualified class name. e.g. com.phonegap.FooBar
* @param action String containt the action that the class is supposed to perform. This is
* passed to the plugin execute method and it is up to the plugin developer
* how to deal with it.
* @param callbackId String containing the id of the callback that is execute in JavaScript if
* this is an async plugin call.
* @param args An Array literal string containing any arguments needed in the
* plugin execute method.
* @param async Boolean indicating whether the calling JavaScript code is expecting an
* immediate return value. If true, either PhoneGap.callbackSuccess(...) or PhoneGap.callbackError(...)
* is called once the plugin code has executed.
* @return JSON encoded string with a response message and status.
*/
public String exec(final String clazz, final String action, final String callbackId,
final String args, final boolean async) {
final String jsonArgs, final boolean async) {
CommandResult cr = null;
try {
//final WebView wv = this.app;
final String _callbackId = callbackId;
final String[] aargs = args.split("__PHONEGAP__");
Class c = Class.forName(clazz);
Class[] interfaces = c.getInterfaces();
for (int j=0; j<interfaces.length; j++) {
if (interfaces[j].getName().equals("com.phonegap.api.Command")) {
final Command ci = (Command)c.newInstance();
ci.setContext(this.ctx);
if (async) {
// Run this async on the UI thread
app.post(new Runnable() {
public void run() {
CommandResult cr = ci.execute(action, aargs);
if (cr.getStatus() == 0) {
app.loadUrl("javascript:PhoneGap.callbackSuccess('"+callbackId+"', " + cr.getResult()+ ");");
} else {
app.loadUrl("javascript:PhoneGap.callbackFailure('"+callbackId+"', " + cr.getResult() + ");");
}
final JSONArray args = new JSONArray(jsonArgs);
Class c = getClassByName(clazz);
if (isPhoneGapCommand(c)) {
// Create a new instance of the plugin and set the context and webview
final Command plugin = (Command)c.newInstance();
plugin.setContext(this.ctx);
plugin.setView(this.app);
if (async) {
// Run this async on the UI thread so that JavaScript can continue on
app.post(new Runnable() {
public void run() {
// Call execute on the plugin so that it can do it's thing
CommandResult cr = plugin.execute(action, args);
// Check if the
if (cr.getStatus() == 0) {
app.loadUrl(cr.toSuccessCallbackString(callbackId));
} else {
app.loadUrl(cr.toErrorCallbackString(callbackId));
}
});
return "";
} else {
cr = ci.execute(action, aargs);
}
}
});
// Return ""
return "";
} else {
// Call execute on the plugin so that it can do it's thing
cr = plugin.execute(action, args);
}
}
} catch (ClassNotFoundException e) {
@@ -70,11 +94,44 @@ public final class CommandManager {
} catch (InstantiationException e) {
cr = new CommandResult(CommandResult.Status.INSTANTIATIONEXCEPTION,
"{ message: 'InstantiationException', status: "+CommandResult.Status.INSTANTIATIONEXCEPTION.ordinal()+" }");
} catch (JSONException e) {
cr = new CommandResult(CommandResult.Status.JSONEXCEPTION,
"{ message: 'JSONException', status: "+CommandResult.Status.JSONEXCEPTION.ordinal()+" }");
}
// if async we have already returned at this point unless there was an error...
if (async) {
app.loadUrl("javascript:PhoneGap.callbackFailure('"+callbackId+"', " + cr.getResult() + ");");
app.loadUrl(cr.toErrorCallbackString(callbackId));
}
return ( cr != null ? cr.getResult() : "{ status: 0, message: 'all good' }" );
}
/**
*
*
* @param clazz
* @return
* @throws ClassNotFoundException
*/
private Class getClassByName(final String clazz) throws ClassNotFoundException {
return Class.forName(clazz);
}
/**
* Get the interfaces that a class implements and see if it implements the
* com.phonegap.api.Command interface.
*
* @param c The class to check the interfaces of.
* @return Boolean indicating if the class implements com.phonegap.api.Command
*/
private boolean isPhoneGapCommand(Class c) {
boolean isCommand = false;
Class[] interfaces = c.getInterfaces();
for (int j=0; j<interfaces.length; j++) {
if (interfaces[j].getName().equals("com.phonegap.api.Command")) {
isCommand = true;
break;
}
}
return isCommand;
}
}

View File

@@ -17,6 +17,14 @@ public class CommandResult {
return result;
}
public String toSuccessCallbackString(String callbackId) {
return "javascript:PhoneGap.callbackSuccess('"+callbackId+"', " + this.getResult()+ ");";
}
public String toErrorCallbackString(String callbackId) {
return "javascript:PhoneGap.callbackError('"+callbackId+"', " + this.getResult()+ ");";
}
public enum Status {
OK,
CLASSNOTFOUNDEXCEPTION,
@@ -24,6 +32,7 @@ public class CommandResult {
INSTANTIATIONEXCEPTION,
MALFORMEDURLEXCEPTION,
IOEXCEPTION,
INVALIDACTION
INVALIDACTION,
JSONEXCEPTION
}
}

View File

@@ -1,109 +0,0 @@
package com.phonegap.api.impl;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import android.content.Context;
import com.phonegap.api.Command;
import com.phonegap.api.CommandResult;
public final class Cache implements Command {
private Context ctx;
public boolean accept(String action) {
// TODO Auto-generated method stub
return false;
}
public void setContext(Context ctx) {
this.ctx = ctx;
}
public CommandResult execute(String action, String[] args) {
CommandResult.Status status = CommandResult.Status.OK;
String result = "";
String uri = args[0];
String fileName = md5(uri);
if (action.equals("getCachedPathForURI") && args.length == 1)
{
// First check if the file exists already
String fileDir = ctx.getFilesDir().getAbsolutePath();
String filePath = fileDir + "/" + fileName;
File f = new File(filePath);
if (f.exists()) {
result = "{ file: '"+filePath+"', status: 0 }";
} else {
URL u;
InputStream is = null;
DataInputStream dis;
FileOutputStream out = null;
byte[] buffer = new byte[1024];
int length = -1;
try {
u = new URL(uri);
is = u.openStream(); // throws an IOException
dis = new DataInputStream(new BufferedInputStream(is));
out = ctx.openFileOutput(fileName, Context.MODE_PRIVATE);
while ((length = dis.read(buffer)) != -1) {
out.write(buffer, 0, length);
}
out.flush();
result = "{ file: '"+fileName+"', status: 0 }";
} catch (MalformedURLException e) {
status = CommandResult.Status.MALFORMEDURLEXCEPTION;
result = "{ message: 'MalformedURLException', status: "+status.ordinal()+" }";
} catch (IOException e) {
status = CommandResult.Status.IOEXCEPTION;
result = "{ message: 'IOException', status: "+status.ordinal()+" }";
} finally {
try {
is.close();
out.close();
} catch (IOException e) {
status = CommandResult.Status.IOEXCEPTION;
result = "{ message: 'IOException', status: "+status.ordinal()+" }";
}
}
}
} else {
status = CommandResult.Status.INVALIDACTION;
result = "{ message: 'InvalidAction', status: "+status.ordinal()+" }";
}
return new CommandResult(status, result);
}
public String md5(String s) {
try {
// Create MD5 Hash
MessageDigest digest = java.security.MessageDigest.getInstance("MD5");
digest.update(s.getBytes());
byte messageDigest[] = digest.digest();
// Create Hex String
StringBuffer hexString = new StringBuffer();
for (int i=0; i<messageDigest.length; i++)
hexString.append(Integer.toHexString(0xFF & messageDigest[i]));
return hexString.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
}