package mil.nga.dice;
import android.Manifest;
import android.app.Activity;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.util.Log;
import android.webkit.WebView;
import com.fangjian.WebViewJavascriptBridge;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.maps.model.LatLng;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Map;
import mil.nga.dice.report.GeoPackageWebViewClient;
import mil.nga.dice.report.Report;
import mil.nga.dice.report.ReportDetailActivity;
import mil.nga.dice.report.ReportManager;
import mil.nga.geopackage.BoundingBox;
/**
*/
public class JavaScriptAPI implements ConnectionCallbacks, OnConnectionFailedListener {
private static final String TAG = "JavaScriptAPI";
public static JavaScriptAPI addTo(WebView webView, Report report, Activity context, GeoPackageWebViewClient geoPackageClient) {
return new JavaScriptAPI(context, report, webView, geoPackageClient);
}
private Activity mActivity;
private Report mReport;
private WebView mWebView;
private File exportDirectory = new File(ReportManager.getInstance().getReportsDir(), "export");
private WebViewJavascriptBridge bridge;
private GoogleApiClient mGoogleApiClient;
private GeoPackageWebViewClient geoPackage;
private WebViewJavascriptBridge.WVJBResponseCallback jsCallbackAfterPermissions;
private JavaScriptAPI(Activity a, Report r, WebView w, GeoPackageWebViewClient geoPackageClient) {
mActivity = a;
mReport = r;
mWebView = w;
geoPackage = geoPackageClient;
buildGoogleApiClient();
mGoogleApiClient.connect();
Log.i(TAG, "Configuring JavascriptBridge");
bridge = new WebViewJavascriptBridge(mActivity, mWebView, new UserServerHandler(), geoPackage);
bridge.registerHandler("getLocation", new WebViewJavascriptBridge.WVJBHandler() {
@Override
public void handle(String data, WebViewJavascriptBridge.WVJBResponseCallback jsCallback) {
geolocate(jsCallback);
}
});
bridge.registerHandler("saveToFile", new WebViewJavascriptBridge.WVJBHandler() {
@Override
public void handle(String data, WebViewJavascriptBridge.WVJBResponseCallback jsCallback) {
exportJSON(data, jsCallback);
}
});
bridge.registerHandler("click", new WebViewJavascriptBridge.WVJBHandler() {
@Override
public void handle(String data, WebViewJavascriptBridge.WVJBResponseCallback jsCallback) {
click(data, jsCallback);
}
});
}
private void exportJSON(String data, WebViewJavascriptBridge.WVJBResponseCallback jsCallback){
Log.i(TAG, "Bridge received a call to export data");
if (jsCallback != null) {
File export = new File(exportDirectory, mReport.getTitle() + "_export.json");
if (!exportDirectory.exists()) {
exportDirectory.mkdir();
}
try {
export.createNewFile();
FileOutputStream fOut = new FileOutputStream(export);
fOut.write(data.getBytes());
fOut.flush();
fOut.close();
jsCallback.callback("{\"success\":true,\"message\":\"Exported your data to the DICE folder on your SD card.\"}");
Intent emailIntent = new Intent(Intent.ACTION_SEND);
emailIntent.setType("text/plain");
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, mReport.getTitle() + " export");
emailIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(export));
mActivity.startActivity(Intent.createChooser(emailIntent, "Email your export"));
} catch (IOException e) {
e.printStackTrace();
jsCallback.callback("{\"success\":false,\"message\":\"There was a problem exporting your data, please try again.\"}");
// TODO: send an error object back through the webview so the user can handle it
}
}
}
private void geolocate(WebViewJavascriptBridge.WVJBResponseCallback jsCallback){
Log.i(TAG, "Bridge received a call to getLocation");
if (jsCallback != null) {
Location location = null;
jsCallbackAfterPermissions = jsCallback;
if (ContextCompat.checkSelfPermission(mActivity, android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
geolocateWithPermissions(true);
}else{
if (ActivityCompat.shouldShowRequestPermissionRationale(mActivity, android.Manifest.permission.ACCESS_FINE_LOCATION)) {
new AlertDialog.Builder(mActivity, R.style.AppCompatAlertDialogStyle)
.setTitle(R.string.location_access_rational_title)
.setMessage(R.string.location_access_rational_message)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(mActivity, new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION}, ReportDetailActivity.PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
}
})
.create()
.show();
} else {
ActivityCompat.requestPermissions(mActivity, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, ReportDetailActivity.PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION);
}
}
}
}
/**
* GeoLocate with the permission either granted or denied
* @param granted true if granted
*/
public void geolocateWithPermissions(boolean granted){
if(granted) {
try {
Location location = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
if (location != null) {
// get the users location and send it back
jsCallbackAfterPermissions.callback("{\"success\":true,\"lat\":\"" + location.getLatitude() + "\",\"lon\":\"" + location.getLongitude() + "\"}");
} else {
jsCallbackAfterPermissions.callback("{\"success\":false,\"message\":\"DICE could not determine your location. Ensure location services are enabled on your device.\"}");
}
}catch(SecurityException e){
Log.e(TAG, "Failed to get user location after permissions were granted", e);
jsCallbackAfterPermissions.callback("{\"success\":false,\"message\":\"DICE does not have location permissions to determine your location.\"}");
}
}else{
jsCallbackAfterPermissions.callback("{\"success\":false,\"message\":\"DICE does not have location permissions to determine your location.\"}");
// If the user has declared to no longer get asked about permissions
if (!ActivityCompat.shouldShowRequestPermissionRationale(mActivity, Manifest.permission.ACCESS_FINE_LOCATION)) {
new AlertDialog.Builder(mActivity, R.style.AppCompatAlertDialogStyle)
.setTitle(mActivity.getResources().getString(R.string.location_access_denied_title))
.setMessage(mActivity.getResources().getString(R.string.location_access_denied_message))
.setPositiveButton(R.string.settings, new Dialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", mActivity.getPackageName(), null));
mActivity.startActivity(intent);
}
})
.setNegativeButton(android.R.string.cancel, null)
.show();
}
}
}
private void click(String data, WebViewJavascriptBridge.WVJBResponseCallback jsCallback){
Log.i(TAG, "Bridge received request to query on a map click: " + data);
if (jsCallback != null) {
try {
JSONObject jsonObject = new JSONObject(data);
double lat = jsonObject.getDouble("lat");
double lon = jsonObject.getDouble("lng");
double zoom = jsonObject.getDouble("zoom");
JSONObject bounds = jsonObject.getJSONObject("bounds");
if(bounds != null) {
LatLng location = new LatLng(lat, lon);
BoundingBox mapBounds = null;
JSONObject southWest = bounds.getJSONObject("_southWest");
JSONObject northEast = bounds.getJSONObject("_northEast");
if(southWest != null && northEast != null){
double minLon = southWest.getDouble("lng");
double maxLon = northEast.getDouble("lng");
double minLat = southWest.getDouble("lat");
double maxLat = northEast.getDouble("lat");
mapBounds = new BoundingBox(minLon, maxLon, minLat, maxLat);
}
if(mapBounds != null) {
// Include points by default
boolean includePoints = jsonObject.optBoolean("points", true);
// Do not include geometries by default
boolean includeGeometries = jsonObject.optBoolean("geometries", true);
Map<String, Object> clickData = geoPackage.mapClickTableData(location, zoom, mapBounds, includePoints, includeGeometries);
if(clickData == null){
jsCallback.callback("{\"success\":true,\"message\":\"\"}");
}else{
try {
JSONObject jsonData = new JSONObject(clickData);
jsCallback.callback("{\"success\":true,\"message\":" + jsonData.toString() + "}");
}catch(Exception e2){
Log.e(JavaScriptAPI.class.getSimpleName(), "Failed to build JSON response", e2);
jsCallback.callback("{\"success\":false,\"message\":\"DICE failed to build JSON response. " + e2.getMessage() + "\"}");
}
}
}else{
jsCallback.callback("{\"success\":false,\"message\":\"Data bounds did not contain correct _southWest and _northWest values\"}");
}
}else{
jsCallback.callback("{\"success\":false,\"message\":\"Data did not contain bounds value\"}");
}
}catch(JSONException e){
Log.e(JavaScriptAPI.class.getSimpleName(), "Failed to parse JSON: " + data, e);
jsCallback.callback("{\"success\":false,\"message\":\"DICE failed to parse JSON. " + e.getMessage() + "\"}");
}
}
}
public void removeFromWebView() {
geoPackage.close();
mGoogleApiClient.disconnect();
mGoogleApiClient = null;
mWebView = null;
bridge = null;
}
class UserServerHandler implements WebViewJavascriptBridge.WVJBHandler {
@Override
public void handle(String data, WebViewJavascriptBridge.WVJBResponseCallback jsCallback) {
Log.i(TAG, "DICE Android received a message from Javascript");
if (jsCallback != null) {
jsCallback.callback("Response from DICE");
}
}
}
protected synchronized void buildGoogleApiClient() {
mGoogleApiClient = new GoogleApiClient.Builder(mActivity.getApplicationContext())
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}
@Override
public void onConnected(Bundle bundle) {
}
@Override
public void onConnectionSuspended(int i) {
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
}
}