de.htw.ar.treasurehuntar.AbstractArchitectActivity.java Source code

Java tutorial

Introduction

Here is the source code for de.htw.ar.treasurehuntar.AbstractArchitectActivity.java

Source

package de.htw.ar.treasurehuntar;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.hardware.SensorManager;
import android.location.Location;
import android.location.LocationListener;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.webkit.WebView;
import android.widget.Toast;
import com.google.android.glass.touchpad.GestureDetector;
import com.wikitude.architect.ArchitectView;
import com.wikitude.architect.ArchitectView.ArchitectConfig;
import com.wikitude.architect.ArchitectView.ArchitectUrlListener;
import com.wikitude.architect.ArchitectView.SensorAccuracyChangeListener;

import java.io.IOException;

/**
 * Abstract activity which handles live-cycle events.
 * Feel free to extend from this activity when setting up your own AR-Activity
 */
public abstract class AbstractArchitectActivity extends FragmentActivity {

    /**
     * equals "AR.CONST.UNKNOWN_ALTITUDE" in JavaScript (compare AR.GeoLocation specification)
     * Use "AR.CONST.UNKNOWN_ALTITUDE" to tell ARchitect that altitude of places should be on user level.
     * Be aware to handle altitude properly in locationManager in case you use valid POI altitude
     * value (e.g. pass altitude only if GPS accuracy is <7m).
     */
    public static final float UNKNOWN_ALTITUDE = -32768f;

    /**
     * extras key for architect-url to load, usually already known upfront, can be relative folder to assets (myWorld.html --> assets/myWorld.html is loaded) or web-url ("http://myserver.com/myWorld.html"). Note that argument passing is only possible via web-url
     */
    protected static final String EXTRAS_KEY_ACTIVITY_ARCHITECT_WORLD_URL = "activityArchitectWorldUrl";

    /**
     * extras key for activity title, usually static and set in Manifest.xml
     */
    protected static final String EXTRAS_KEY_ACTIVITY_TITLE_STRING = "activityTitle";

    /**
     * holds the Wikitude SDK AR-View, this is where camera, markers, compass, 3D models etc. are rendered
     */
    protected ArchitectView architectView;

    /**
     * sensor accuracy listener in case you want to display calibration hints
     */
    protected SensorAccuracyChangeListener sensorAccuracyListener;

    /**
     * last known location of the user, used internally for content-loading after user location was fetched
     */
    protected Location lastKnownLocation;

    /**
     * sample location strategy, you may implement a more sophisticated approach too
     */
    protected LocationProvider locationProvider;

    /**
     * location listener receives location updates and must forward them to the architectView
     */
    protected LocationListener locationListener;

    /**
     * urlListener handling "document.location= 'architectsdk://...' " calls in JavaScript"
     */
    protected ArchitectUrlListener urlListener;

    /**
     * radius in m
     */
    public static final int MAX_RADIUS = 2000;

    /**
     * last time the calibration toast was shown, this avoids too many toast shown when compass needs calibration
     */
    private long lastCalibrationToastShownTimeMillis = System.currentTimeMillis();

    protected GestureDetector mGestureDetector;

    /**
     * Called when the activity is first created.
     */
    @SuppressLint("NewApi")
    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // pressing volume up/down should cause music volume changes
        this.setVolumeControlStream(AudioManager.STREAM_MUSIC);

        this.setContentView(this.getContentViewId());
        this.setTitle(this.getActivityTitle());

        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        Log.i("architect", "create");

        mGestureDetector = createGestureDetector(this);

        //
        // this enables remote debugging of a WebView on Android 4.4+ when debugging = true in AndroidManifest.xml
        // If you get a compile time error here, ensure to have SDK 19+ used in your ADT/Eclipse.
        // You may even delete this block in case you don't need remote debugging or don't have an Android 4.4+ device in place.
        // Details: https://developers.google.com/chrome-developer-tools/docs/remote-debugging
        //
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            if (0 != (getApplicationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE)) {
                WebView.setWebContentsDebuggingEnabled(true);
            }
        }

        // set AR-view for life-cycle notifications etc.
        this.architectView = (ArchitectView) this.findViewById(this.getArchitectViewId());

        // pass SDK key if you have one, this one is only valid for this package identifier and must not be used somewhere else
        final ArchitectConfig config = new ArchitectConfig(this.getWikitudeSDKLicenseKey());

        try {
            // first mandatory life-cycle notification
            this.architectView.onCreate(config);
        } catch (RuntimeException rex) {
            this.architectView = null;
            Toast.makeText(getApplicationContext(), "can't create Architect View", Toast.LENGTH_SHORT).show();
            Log.e(this.getClass().getName(), "Exception in ArchitectView.onCreate()", rex);
        }

        // set accuracy listener if implemented, you may e.g. show calibration prompt for compass using this listener
        this.sensorAccuracyListener = this.getSensorAccuracyListener();

        // set urlListener, any calls made in JS like "document.location = 'architectsdk://foo?bar=123'" is forwarded to this listener, use this to interact between JS and native Android activity/fragment
        this.urlListener = this.getUrlListener();

        // register valid urlListener in architectView, ensure this is set before content is loaded to not miss any event
        if (this.urlListener != null && this.architectView != null) {
            this.architectView.registerUrlListener(this.getUrlListener());
        }

        // listener passed over to locationProvider, any location update is handled here
        this.locationListener = new LocationListener() {

            @Override
            public void onStatusChanged(String provider, int status, Bundle extras) {
            }

            @Override
            public void onProviderEnabled(String provider) {
            }

            @Override
            public void onProviderDisabled(String provider) {
            }

            @Override
            public void onLocationChanged(final Location location) {
                // forward location updates fired by LocationProvider to architectView, you can set lat/lon from any location-strategy
                if (location != null) {
                    Log.i("location", location.toString());
                    // sore last location as member, in case it is needed somewhere (in e.g. your adjusted project)
                    AbstractArchitectActivity.this.lastKnownLocation = location;
                    if (AbstractArchitectActivity.this.architectView != null) {
                        // check if location has altitude at certain accuracy level & call right architect method (the one with altitude information)
                        if (location.hasAltitude() && location.hasAccuracy() && location.getAccuracy() < 7) {
                            AbstractArchitectActivity.this.architectView.setLocation(location.getLatitude(),
                                    location.getLongitude(), location.getAltitude(), location.getAccuracy());
                        } else {
                            AbstractArchitectActivity.this.architectView.setLocation(location.getLatitude(),
                                    location.getLongitude(),
                                    location.hasAccuracy() ? location.getAccuracy() : 1000);
                        }
                    }
                }
            }
        };

        // locationProvider used to fetch user position
        this.locationProvider = new LocationProvider(this, this.locationListener);
    }

    /*
     * Send generic motion events to the gesture detector
     */
    @Override
    public boolean onGenericMotionEvent(MotionEvent event) {
        return mGestureDetector != null && mGestureDetector.onMotionEvent(event);
    }

    @Override
    protected void onPostCreate(final Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);

        if (this.architectView != null) {

            // call mandatory live-cycle method of architectView
            this.architectView.onPostCreate();

            try {
                // load content via url in architectView, ensure '<script src="architect://architect.js"></script>' is part of this HTML file, have a look at wikitude.com's developer section for API references
                this.architectView.load(this.getARchitectWorldPath());

                // set the culling distance - meaning: the maximum distance to render geo-content
                this.architectView.setCullingDistance(MAX_RADIUS);

            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

    @Override
    protected void onResume() {
        super.onResume();

        // call mandatory live-cycle method of architectView
        if (this.architectView != null) {
            this.architectView.onResume();

            // register accuracy listener in architectView, if set
            if (this.sensorAccuracyListener != null) {
                this.architectView.registerSensorAccuracyChangeListener(this.sensorAccuracyListener);
            }
        }

        // tell locationProvider to resume, usually location is then (again) fetched, so the GPS indicator appears in status bar
        if (this.locationProvider != null) {
            this.locationProvider.onResume();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();

        // call mandatory live-cycle method of architectView
        if (this.architectView != null) {
            this.architectView.onPause();

            // unregister accuracy listener in architectView, if set
            if (this.sensorAccuracyListener != null) {
                this.architectView.unregisterSensorAccuracyChangeListener(this.sensorAccuracyListener);
            }
        }

        // tell locationProvider to pause, usually location is then no longer fetched, so the GPS indicator disappears in status bar
        if (this.locationProvider != null) {
            this.locationProvider.onPause();
        }
    }

    @Override
    protected void onStop() {
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        // call mandatory live-cycle method of architectView
        if (this.architectView != null) {
            this.architectView.onDestroy();
        }
    }

    @Override
    public void onLowMemory() {
        super.onLowMemory();
        if (this.architectView != null) {
            this.architectView.onLowMemory();
        }
    }

    /**
     * Gestuure detecttion
     *
     * @param context
     * @return
     */
    protected abstract GestureDetector createGestureDetector(Context context);

    public SensorAccuracyChangeListener getSensorAccuracyListener() {
        return new SensorAccuracyChangeListener() {
            @Override
            public void onCompassAccuracyChanged(int accuracy) {
                /* UNRELIABLE = 0, LOW = 1, MEDIUM = 2, HIGH = 3 */
                if (accuracy < SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM && isFinishing()
                        && System.currentTimeMillis() - lastCalibrationToastShownTimeMillis > 5 * 1000) {
                    Toast.makeText(AbstractArchitectActivity.this, R.string.compass_accuracy_low, Toast.LENGTH_LONG)
                            .show();
                    lastCalibrationToastShownTimeMillis = System.currentTimeMillis();
                }
            }
        };
    }

    /**
     * title shown in activity
     *
     * @return
     */
    public String getActivityTitle() {
        return (getIntent().getExtras() != null
                && getIntent().getExtras().get(EXTRAS_KEY_ACTIVITY_TITLE_STRING) != null)
                        ? getIntent().getExtras().getString(EXTRAS_KEY_ACTIVITY_TITLE_STRING)
                        : "Test-World";
    }

    /**
     * path to the architect-file (AR-Experience HTML) to launch
     *
     * @return
     */
    public String getARchitectWorldPath() {
        return getIntent().getExtras().getString(EXTRAS_KEY_ACTIVITY_ARCHITECT_WORLD_URL);
    }

    /**
     * url listener fired once e.g. 'document.location = "architectsdk://foo?bar=123"' is called in JS
     *
     * @return
     */
    public abstract ArchitectUrlListener getUrlListener();

    /**
     * @return layout id of your layout.xml that holds an ARchitect View, e.g. R.layout.camview
     */
    public int getContentViewId() {
        return R.layout.main_layout;
    }

    /**
     * @return Wikitude SDK license key, checkout www.wikitude.com for details
     */
    public String getWikitudeSDKLicenseKey() {
        return Constants.WIKITUDE_SDK_KEY;
    }

    /**
     * @return layout-id of architectView, e.g. R.id.architectView
     */
    public int getArchitectViewId() {
        return R.id.architectView;
    }

    /**
     * call JavaScript in architectView
     *
     * @param methodName the js method name
     * @param arguments  the argument
     */
    protected void callJavaScript(final String methodName, final String[] arguments) {
        final StringBuilder argumentsString = new StringBuilder("");
        for (int i = 0; i < arguments.length; i++) {
            argumentsString.append(arguments[i]);
            if (i < arguments.length - 1) {
                argumentsString.append(", ");
            }
        }

        if (this.architectView != null) {
            final String js = (methodName + "( " + argumentsString.toString() + " );");
            this.architectView.callJavascript(js);
        }
    }

    /**
     * call JavaScript in architectView without params
     *
     * @param methodName the js method name
     */
    protected void callJavaScript(final String methodName) {
        if (this.architectView != null) {
            final String js = (methodName + "();");
            this.architectView.callJavascript(js);
        }
    }

}