mirror of
https://github.com/purplecabbage/phonegap-plugins.git
synced 2026-04-24 03:00:11 -04:00
428 lines
14 KiB
Java
428 lines
14 KiB
Java
package com.wikitude.phonegap;
|
|
|
|
import java.io.IOException;
|
|
|
|
import org.json.JSONArray;
|
|
import org.json.JSONException;
|
|
import org.json.JSONObject;
|
|
|
|
import android.app.Activity;
|
|
import android.view.View;
|
|
import android.view.ViewGroup;
|
|
import android.view.ViewGroup.LayoutParams;
|
|
import android.view.ViewManager;
|
|
|
|
import com.phonegap.api.Plugin;
|
|
import com.phonegap.api.PluginResult;
|
|
import com.wikitude.architect.ArchitectUrlListener;
|
|
import com.wikitude.architect.ArchitectView;
|
|
import com.wikitude.architect.ArchitectView.ArchitectConfig;
|
|
|
|
|
|
|
|
/**
|
|
* Basic PhoneGap Wikitude ARchitect Plugin
|
|
*
|
|
* You must add "<plugin name="WikitudePlugin" value="com.wikitude.phonegap.WikitudePlugin"/>"
|
|
* in config.xml to enable this plug-in in your project
|
|
*
|
|
* Also ensure to have wikitudesdk.jar in your libs folder
|
|
*
|
|
* Note:
|
|
* This plug-in is written under Apache License, Version 2.0
|
|
* http://www.apache.org/licenses/LICENSE-2.0.html
|
|
*
|
|
* @version 1.0.0
|
|
* @author Wikitude GmbH; www.wikitude.com
|
|
*/
|
|
public class WikitudePlugin extends Plugin implements ArchitectUrlListener {
|
|
|
|
/** PhoneGap-root to Android-app-assets folder ; e.g. use "assets/foo.html" as source if you want to load foo.html from your android-project's assets-folder */
|
|
private static final String LOCAL_ASSETS_PATH_ROOT = "assets/";
|
|
|
|
/* various JSON-Object keys*/
|
|
private static final String JSON_KEY_APIKEY = "sdkKey";
|
|
private static final String JSON_KEY_FILE_PATH = "filePath";
|
|
|
|
private static final String JSON_KEY_LOCATION_ALTITUDE = "alt";
|
|
private static final String JSON_KEY_LOCATION_ACCURACY = "acc";
|
|
private static final String JSON_KEY_LOCATION_LATITUDE = "lat";
|
|
private static final String JSON_KEY_LOCATION_LONGITUDE = "lon";
|
|
|
|
/* static action strings */
|
|
|
|
/**
|
|
* opens architect-view (add to view stack)
|
|
*/
|
|
private static final String ACTION_OPEN = "open";
|
|
|
|
/**
|
|
* closes architect-view (remove view stack)
|
|
*/
|
|
private static final String ACTION_CLOSE = "close";
|
|
|
|
/**
|
|
* set visibility of architectView to visible (of present)
|
|
*/
|
|
private static final String ACTION_SHOW = "show";
|
|
|
|
/**
|
|
* set visibility of architectView to invisible (of present)
|
|
*/
|
|
private static final String ACTION_HIDE = "hide";
|
|
|
|
/**
|
|
* inject location information
|
|
*/
|
|
private static final String ACTION_SET_LOCATION = "setLocation";
|
|
|
|
/**
|
|
* callback for uri-invocations
|
|
*/
|
|
private static final String ACTION_ON_URLINVOKE = "onUrlInvoke";
|
|
|
|
/**
|
|
* life-cycle notification for resume
|
|
*/
|
|
private static final String ACTION_ON_RESUME = "onResume";
|
|
|
|
/**
|
|
* life-cycle notification for pause
|
|
*/
|
|
private static final String ACTION_ON_PAUSE = "onPause";
|
|
|
|
/**
|
|
* check if view is on view-stack (no matter if visible or not)
|
|
*/
|
|
private static final String ACTION_STATE_ISOPEN = "isOpen";
|
|
|
|
/**
|
|
* opens architect-view (add to view stack)
|
|
*/
|
|
private static final String ACTION_IS_DEVICE_SUPPORTED = "isDeviceSupported";
|
|
|
|
/**
|
|
* check if view is on view-stack (no matter if visible or not)
|
|
*/
|
|
private static final String ACTION_CALL_JAVASCRIPT = "callJavascript";
|
|
|
|
/**
|
|
* the Wikitude ARchitectview
|
|
*/
|
|
private ArchitectView architectView;
|
|
|
|
/**
|
|
* callback-Id of url-invocation method
|
|
*/
|
|
private String urlInvokeCallbackId = null;
|
|
|
|
/**
|
|
* callback-id of "open"-action method
|
|
*/
|
|
private String openCallbackId = null;
|
|
|
|
@Override
|
|
public PluginResult execute( final String action, final JSONArray args, final String callbackId ) {
|
|
|
|
/* hide architect-view -> destroy and remove from activity */
|
|
if ( WikitudePlugin.ACTION_CLOSE.equals( action ) ) {
|
|
if ( this.architectView != null ) {
|
|
this.cordova.getActivity().runOnUiThread( new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
removeArchitectView();
|
|
}
|
|
} );
|
|
return new PluginResult( PluginResult.Status.OK, action + ": architectView is present" );
|
|
}
|
|
else {
|
|
return new PluginResult( PluginResult.Status.ERROR, action + ": architectView is not present" );
|
|
}
|
|
}
|
|
|
|
/* return success only if view is opened (no matter if visible or not) */
|
|
if ( WikitudePlugin.ACTION_STATE_ISOPEN.equals( action ) ) {
|
|
if ( this.architectView != null ) {
|
|
return new PluginResult( PluginResult.Status.OK, action + ": architectView is present" );
|
|
} else {
|
|
return new PluginResult( PluginResult.Status.ERROR, action + ": architectView is not present" );
|
|
}
|
|
}
|
|
|
|
/* return success only if view is opened (no matter if visible or not) */
|
|
if ( WikitudePlugin.ACTION_IS_DEVICE_SUPPORTED.equals( action ) ) {
|
|
if ( ArchitectView.isDeviceSupported( this.cordova.getActivity() ) ) {
|
|
return new PluginResult( PluginResult.Status.OK, action + ": this device is ARchitect-ready" );
|
|
} else {
|
|
return new PluginResult( PluginResult.Status.ERROR, action + ": Sorry, this device is NOT ARchitect-ready" );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* life-cycle's RESUME */
|
|
if ( WikitudePlugin.ACTION_ON_RESUME.equals( action ) ) {
|
|
|
|
if ( this.architectView != null ) {
|
|
this.cordova.getActivity().runOnUiThread( new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
WikitudePlugin.this.architectView.onResume();
|
|
}
|
|
} );
|
|
|
|
return new PluginResult( PluginResult.Status.OK, action + ": architectView is present" );
|
|
}
|
|
return new PluginResult( PluginResult.Status.ERROR, action + ": architectView is not present" );
|
|
}
|
|
|
|
/* life-cycle's PAUSE */
|
|
if ( WikitudePlugin.ACTION_ON_PAUSE.equals( action ) ) {
|
|
if ( architectView != null ) {
|
|
this.cordova.getActivity().runOnUiThread( new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
WikitudePlugin.this.architectView.onPause();
|
|
}
|
|
} );
|
|
|
|
return new PluginResult( PluginResult.Status.OK, action + ": architectView is present" );
|
|
}
|
|
return new PluginResult( PluginResult.Status.ERROR, action + ": architectView is not present" );
|
|
}
|
|
|
|
/* set visibility to "visible", return error if view is null */
|
|
if ( WikitudePlugin.ACTION_SHOW.equals( action ) ) {
|
|
|
|
if ( this.architectView != null ) {
|
|
this.architectView.setVisibility( View.VISIBLE );
|
|
return new PluginResult( PluginResult.Status.OK, action + ": architectView is present" );
|
|
} else {
|
|
return new PluginResult( PluginResult.Status.ERROR, action + ": architectView is not present" );
|
|
}
|
|
}
|
|
|
|
/* set visibility to "invisible", return error if view is null */
|
|
if ( WikitudePlugin.ACTION_HIDE.equals( action ) ) {
|
|
|
|
if ( this.architectView != null ) {
|
|
this.architectView.setVisibility( View.INVISIBLE );
|
|
return new PluginResult( PluginResult.Status.OK, action + ": architectView is present" );
|
|
} else {
|
|
return new PluginResult( PluginResult.Status.ERROR, action + ": architectView is not present" );
|
|
}
|
|
}
|
|
|
|
/* define call-back for url-invocations */
|
|
if ( WikitudePlugin.ACTION_ON_URLINVOKE.equals( action ) ) {
|
|
this.urlInvokeCallbackId = callbackId;
|
|
PluginResult result = new PluginResult( PluginResult.Status.NO_RESULT, action + ": registered callback" );
|
|
result.setKeepCallback( true );
|
|
return result;
|
|
}
|
|
|
|
/* location update */
|
|
if ( WikitudePlugin.ACTION_SET_LOCATION.equals( action ) ) {
|
|
if ( this.architectView != null ) {
|
|
try {
|
|
String arrStr = args.getString( 0 );
|
|
JSONObject arr = new JSONObject( arrStr );
|
|
final double lat = arr.getDouble( WikitudePlugin.JSON_KEY_LOCATION_LATITUDE );
|
|
final double lon = arr.getDouble( WikitudePlugin.JSON_KEY_LOCATION_LONGITUDE );
|
|
Object altObj = arr.get( WikitudePlugin.JSON_KEY_LOCATION_ALTITUDE );
|
|
float alt = Float.MIN_VALUE;
|
|
|
|
if ( altObj != null && altObj instanceof Double ) {
|
|
alt = ((Double)altObj).floatValue();
|
|
}
|
|
|
|
final float altitude = alt;
|
|
|
|
final Double acc = arr.getDouble( WikitudePlugin.JSON_KEY_LOCATION_ACCURACY );
|
|
if ( this.cordova != null && this.cordova.getActivity() != null ) {
|
|
this.cordova.getActivity().runOnUiThread( new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
if ( acc != null ) {
|
|
WikitudePlugin.this.architectView.setLocation( lat, lon, altitude, acc.floatValue() );
|
|
} else {
|
|
WikitudePlugin.this.architectView.setLocation( lat, lon, altitude );
|
|
}
|
|
}
|
|
} );
|
|
}
|
|
|
|
} catch ( Exception e ) {
|
|
return new PluginResult( PluginResult.Status.ERROR, action + ": exception thrown, " + e != null ? e.getMessage() : "(exception is NULL)" );
|
|
}
|
|
return new PluginResult( PluginResult.Status.OK, action + ": updated location" );
|
|
}
|
|
|
|
/* return error if there is no architect-view active*/
|
|
return new PluginResult( PluginResult.Status.ERROR, action + ": architectview is not active" );
|
|
}
|
|
|
|
if ( WikitudePlugin.ACTION_CALL_JAVASCRIPT.equals( action ) ) {
|
|
if ( this.architectView != null ) {
|
|
String logMsg = null;
|
|
try {
|
|
final String callJS = args.getString( 0 );
|
|
logMsg = callJS;
|
|
this.cordova.getActivity().runOnUiThread( new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
WikitudePlugin.this.architectView.callJavascript( callJS );
|
|
}
|
|
} );
|
|
|
|
} catch ( JSONException je ) {
|
|
return new PluginResult( PluginResult.Status.ERROR, action + ": exception thrown, " + je != null ? je.getMessage() : "(exception is NULL)" );
|
|
}
|
|
return new PluginResult( PluginResult.Status.OK, action + ": called js, '" + logMsg + "'" );
|
|
} else {
|
|
return new PluginResult( PluginResult.Status.ERROR, action + ": architectview is not active" );
|
|
}
|
|
}
|
|
|
|
|
|
/* initial set-up, show ArchitectView full-screen in current screen/activity */
|
|
if ( WikitudePlugin.ACTION_OPEN.equals( action ) ) {
|
|
this.openCallbackId = callbackId;
|
|
PluginResult result = null;
|
|
String arrStr = null;
|
|
|
|
try {
|
|
arrStr = args.getString( 0 );
|
|
|
|
// arrStr = arrStr.substring( 1, arrStr.length() - 1 );
|
|
|
|
JSONObject arr = new JSONObject( arrStr );
|
|
|
|
final String apiKey = arr.getString( WikitudePlugin.JSON_KEY_APIKEY );
|
|
final String filePath = arr.getString( WikitudePlugin.JSON_KEY_FILE_PATH );
|
|
|
|
this.cordova.getActivity().runOnUiThread( new Runnable() {
|
|
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
WikitudePlugin.this.addArchitectView( apiKey, filePath );
|
|
|
|
/* call success method once architectView was added successfully */
|
|
if ( WikitudePlugin.this.openCallbackId != null ) {
|
|
PluginResult result = new PluginResult( PluginResult.Status.OK );
|
|
result.setKeepCallback( false );
|
|
WikitudePlugin.this.success( result, WikitudePlugin.this.openCallbackId );
|
|
}
|
|
} catch ( Exception e ) {
|
|
/* in case "addArchitectView" threw an exception -> notify callback method asynchronously */
|
|
WikitudePlugin.this.error( e != null ? e.getMessage() : "Exception is 'null'", WikitudePlugin.this.openCallbackId );
|
|
}
|
|
}
|
|
} );
|
|
|
|
} catch ( Exception e ) {
|
|
result = new PluginResult( PluginResult.Status.ERROR, action + ": exception thown, " + e != null ? e.getMessage() : "(exception is NULL)" );
|
|
result.setKeepCallback( false );
|
|
return result;
|
|
}
|
|
|
|
/* adding architect-view is done in separate thread, ensure to setKeepCallback so one can call success-method properly later on */
|
|
result = new PluginResult( PluginResult.Status.NO_RESULT, action + ": no result required, just registered callback-method" );
|
|
result.setKeepCallback( true );
|
|
|
|
return result;
|
|
}
|
|
|
|
/* fall-back return value */
|
|
return new PluginResult( PluginResult.Status.ERROR, "no such action: " + action );
|
|
}
|
|
|
|
/**
|
|
* called when url was invoked in architectView (by e.g. calling document.location = "myprotocoll://foo";
|
|
* @param url the invoked url (e.g. "myprotocoll://foo")
|
|
* @return true if call was handled properly
|
|
*/
|
|
@Override
|
|
public boolean urlWasInvoked( String url ) {
|
|
|
|
/* call callback-method if set*/
|
|
if ( this.urlInvokeCallbackId != null ) {
|
|
try {
|
|
/* pass called url as String to callback-method */
|
|
PluginResult res = new PluginResult( PluginResult.Status.OK, url );
|
|
res.setKeepCallback( true );
|
|
this.success( res, this.urlInvokeCallbackId );
|
|
return true;
|
|
} catch ( Exception e ) {
|
|
this.error( "invalid url invoked: " + url, this.urlInvokeCallbackId );
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* hides/removes ARchitect-View completely
|
|
* @return true if successful, false otherwise
|
|
*/
|
|
private boolean removeArchitectView() {
|
|
if ( this.architectView != null ) {
|
|
/* fake life-cycle calls, because activity is already up and running */
|
|
this.architectView.onPause();
|
|
this.architectView.onDestroy();
|
|
this.architectView.setVisibility( View.INVISIBLE );
|
|
((ViewManager)this.architectView.getParent()).removeView( this.architectView );
|
|
this.architectView = null;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Architect-Configuration required for proper set-up
|
|
* @param apiKey
|
|
* @return
|
|
*/
|
|
protected ArchitectConfig getArchitectConfig( final String apiKey ) {
|
|
/* no special set-up required in default Wikitude-Plugin, further things required in advanced usage (e.g. Vuforia Image Recognition) */
|
|
return new ArchitectConfig( apiKey );
|
|
}
|
|
|
|
/**
|
|
* add architectView to current screen
|
|
* @param apiKey developers's api key to use (hides watermarking/intro-animation if it matches your package-name)
|
|
* @param filePath the url (starting with http:// for online use; starting with LOCAL_ASSETS_PATH_ROOT if oyu want to load assets within your app-assets folder)
|
|
* @throws IOException might be thrown from ARchitect-SDK
|
|
*/
|
|
@SuppressWarnings("deprecation")
|
|
private void addArchitectView( final String apiKey, String filePath ) throws IOException {
|
|
if ( this.architectView == null ) {
|
|
this.architectView = new ArchitectView( (Activity)this.ctx.getContext() );
|
|
|
|
/* add content view and fake initial life-cycle */
|
|
((Activity)this.ctx.getContext()).addContentView( this.architectView, new ViewGroup.LayoutParams( LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT ) );
|
|
|
|
/* fake life-cycle calls, because activity is already up and running */
|
|
this.architectView.onCreate( getArchitectConfig( apiKey ) );
|
|
this.architectView.onPostCreate();
|
|
|
|
/* register self as url listener to fwd these native calls to PhoneGap */
|
|
this.architectView.registerUrlListener( WikitudePlugin.this );
|
|
|
|
/* load asset from local directory if prefix is used */
|
|
if ( filePath.startsWith( WikitudePlugin.LOCAL_ASSETS_PATH_ROOT ) ) {
|
|
filePath = filePath.substring( WikitudePlugin.LOCAL_ASSETS_PATH_ROOT.length() );
|
|
}
|
|
this.architectView.load( filePath );
|
|
|
|
/* also a fake-life-cycle call (the last one before it is really shown in UI */
|
|
this.architectView.onResume();
|
|
}
|
|
}
|
|
}
|