diff --git a/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js b/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js index d65bb0039..96dde3051 100644 --- a/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js +++ b/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js @@ -1,7 +1,5 @@ navigator.geolocation.getCurrentPosition = function(success, failure) { //override because default implementation requires GPS permission - success(JSON.parse(GBjs.getCurrentPosition())); - failure({ code: 2, message: "POSITION_UNAVAILABLE"}); - + success(JSON.parse(GBjs.getCurrentPosition())) } if (window.Storage){ diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java index 5f98350a2..95802efdd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java @@ -3,7 +3,6 @@ package nodomain.freeyourgadget.gadgetbridge.activities; import android.content.Context; import android.content.Intent; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.support.v4.app.NavUtils; import android.view.MenuItem; @@ -61,41 +60,14 @@ public class ExternalPebbleJSActivity extends GBActivity { @Override public void onViewAttachedToWindow(View v) { - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - // chromium, enable hardware acceleration - v.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } else { - // older android version, disable hardware acceleration - v.setLayerType(View.LAYER_TYPE_SOFTWARE, null); - } - - - String queryString = ""; - if (confUri != null) { - //getting back with configuration data - try { - appUuid = UUID.fromString(confUri.getHost()); - queryString = confUri.getEncodedQuery(); - } catch (IllegalArgumentException e) { - GB.toast("returned uri: " + confUri.toString(), Toast.LENGTH_LONG, GB.ERROR); - } - ((WebView) v).loadUrl("file:///android_asset/app_config/configure.html?" + queryString); - } else { + v.setLayerType(View.LAYER_TYPE_HARDWARE, null); //show configuration - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - myWebView.evaluateJavascript("Pebble.evaluate('showConfiguration');", new ValueCallback() { - @Override - public void onReceiveValue(String s) { - LOG.debug("Callback from showConfiguration", s); - } - }); - } else { - ((WebView) v).loadUrl("javascript:Pebble.evaluate('showConfiguration');"); + myWebView.evaluateJavascript("Pebble.evaluate('showConfiguration');", new ValueCallback() { + @Override + public void onReceiveValue(String s) { + LOG.debug("Callback from showConfiguration: " + s); } - } - - + }); } @Override @@ -108,6 +80,22 @@ public class ExternalPebbleJSActivity extends GBActivity { }); } + @Override + protected void onResume() { + super.onResume(); + String queryString = ""; + if (confUri != null) { + //getting back with configuration data + LOG.debug("WEBVIEW returned config: " + confUri.toString()); + try { + appUuid = UUID.fromString(confUri.getHost()); + queryString = confUri.getEncodedQuery(); + } catch (IllegalArgumentException e) { + GB.toast("returned uri: " + confUri.toString(), Toast.LENGTH_LONG, GB.ERROR); + } + myWebView.loadUrl("file:///android_asset/app_config/configure.html?" + queryString); + } + } @Override protected void onNewIntent(Intent incoming) { incoming.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java index 569b17d08..125aff954 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java @@ -82,8 +82,7 @@ class PebbleIoThread extends GBDeviceIoThread { private int mBytesWritten = -1; private void sendAppMessageJS(GBDeviceEventAppMessage appMessage) { -// WebViewSingleton.runJavascriptInterface(gbDevice, appMessage.appUUID); - WebViewSingleton.appMessage(appMessage.message); + WebViewSingleton.appMessage(appMessage); write(mPebbleProtocol.encodeApplicationMessageAck(appMessage.appUUID, (byte) appMessage.id)); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java index d637fe2c0..c8a24bef3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java @@ -385,16 +385,16 @@ public class PebbleProtocol extends GBDeviceProtocol { super(device); mAppMessageHandlers.put(UUID_MORPHEUZ, new AppMessageHandlerMorpheuz(UUID_MORPHEUZ, PebbleProtocol.this)); mAppMessageHandlers.put(UUID_MISFIT, new AppMessageHandlerMisfit(UUID_MISFIT, PebbleProtocol.this)); - mAppMessageHandlers.put(UUID_PEBBLE_TIMESTYLE, new AppMessageHandlerTimeStylePebble(UUID_PEBBLE_TIMESTYLE, PebbleProtocol.this)); +// mAppMessageHandlers.put(UUID_PEBBLE_TIMESTYLE, new AppMessageHandlerTimeStylePebble(UUID_PEBBLE_TIMESTYLE, PebbleProtocol.this)); //mAppMessageHandlers.put(UUID_PEBSTYLE, new AppMessageHandlerPebStyle(UUID_PEBSTYLE, PebbleProtocol.this)); //mAppMessageHandlers.put(UUID_MARIOTIME, new AppMessageHandlerMarioTime(UUID_MARIOTIME, PebbleProtocol.this)); mAppMessageHandlers.put(UUID_HELTHIFY, new AppMessageHandlerHealthify(UUID_HELTHIFY, PebbleProtocol.this)); mAppMessageHandlers.put(UUID_TREKVOLLE, new AppMessageHandlerTrekVolle(UUID_TREKVOLLE, PebbleProtocol.this)); - mAppMessageHandlers.put(UUID_SQUARE, new AppMessageHandlerSquare(UUID_SQUARE, PebbleProtocol.this)); - mAppMessageHandlers.put(UUID_ZALEWSZCZAK_CROWEX, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_CROWEX, PebbleProtocol.this)); - mAppMessageHandlers.put(UUID_ZALEWSZCZAK_FANCY, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_FANCY, PebbleProtocol.this)); - mAppMessageHandlers.put(UUID_ZALEWSZCZAK_TALLY, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_TALLY, PebbleProtocol.this)); - mAppMessageHandlers.put(UUID_OBSIDIAN, new AppMessageHandlerObsidian(UUID_OBSIDIAN, PebbleProtocol.this)); +// mAppMessageHandlers.put(UUID_SQUARE, new AppMessageHandlerSquare(UUID_SQUARE, PebbleProtocol.this)); +// mAppMessageHandlers.put(UUID_ZALEWSZCZAK_CROWEX, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_CROWEX, PebbleProtocol.this)); +// mAppMessageHandlers.put(UUID_ZALEWSZCZAK_FANCY, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_FANCY, PebbleProtocol.this)); +// mAppMessageHandlers.put(UUID_ZALEWSZCZAK_TALLY, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_TALLY, PebbleProtocol.this)); +// mAppMessageHandlers.put(UUID_OBSIDIAN, new AppMessageHandlerObsidian(UUID_OBSIDIAN, PebbleProtocol.this)); } private final HashMap mDatalogSessions = new HashMap<>(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java index 8e925f7d0..a96575165 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -1,19 +1,15 @@ package nodomain.freeyourgadget.gadgetbridge.util; import android.Manifest; -import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.content.MutableContextWrapper; import android.content.pm.PackageManager; import android.location.Criteria; import android.location.Location; import android.location.LocationManager; import android.net.Uri; import android.os.Build; -import android.os.Handler; -import android.os.Looper; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.webkit.ConsoleMessage; @@ -52,6 +48,7 @@ import java.util.Scanner; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.Weather; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; @@ -61,16 +58,16 @@ public class WebViewSingleton { private static final Logger LOG = LoggerFactory.getLogger(WebViewSingleton.class); private static WebView instance = null; - private static JSInterface jsInterface; - private static MutableContextWrapper contextWrapper = null; + private Activity contextWrapper; + private static WebViewSingleton webViewSingleton = new WebViewSingleton(); private WebViewSingleton() { } - public static synchronized WebView createWebView(Context context) { + public static synchronized WebView createWebView(Activity context) { if (instance == null) { - contextWrapper = new MutableContextWrapper(context); - instance = new WebView(contextWrapper); + webViewSingleton.contextWrapper = context; + instance = new WebView(context); instance.setWillNotDraw(true); instance.clearCache(true); instance.setWebViewClient(new GBWebClient()); @@ -86,9 +83,9 @@ public class WebViewSingleton { return instance; } - public static void updateActivityContext(Context context) { + public static void updateActivityContext(Activity context) { if (context instanceof Activity) { - contextWrapper.setBaseContext(context); + webViewSingleton.contextWrapper = context; } } @@ -97,50 +94,46 @@ public class WebViewSingleton { return instance; } - public static void runJavascriptInterface(final GBDevice device, final UUID uuid) { - if (jsInterface == null || !device.equals(jsInterface.device) || !uuid.equals(jsInterface.mUuid)) { - jsInterface = new JSInterface(device, uuid); - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - instance.removeJavascriptInterface("GBjs"); - instance.addJavascriptInterface(jsInterface, "GBjs"); - instance.loadUrl("file:///android_asset/app_config/configure.html"); - } - }); - } else { - LOG.debug("Not replacing the JS in the webview. JS uuid " + jsInterface.mUuid.toString()); - } - } + public static void runJavascriptInterface(GBDevice device, UUID uuid) { - public static void appMessage(String message) { + final JSInterface jsInterface = new JSInterface(device, uuid); - if (instance == null) - return; - - final String appMessage = jsInterface.parseIncomingAppMessage(message); - LOG.debug("to WEBVIEW: " + appMessage); - - new Handler(Looper.getMainLooper()).post(new Runnable() { + webViewSingleton.contextWrapper.runOnUiThread(new Runnable() { @Override public void run() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - instance.evaluateJavascript("Pebble.evaluate('appmessage',[" + appMessage + "]);", new ValueCallback() { - @Override - public void onReceiveValue(String s) { - //TODO: the message should be acked here instead of in PebbleIoThread - LOG.debug("Callback from appmessage", s); - } - }); - } else { - instance.loadUrl("javascript:Pebble.evaluate('appmessage',[" + appMessage + "]);"); - } + instance.removeJavascriptInterface("GBjs"); + instance.addJavascriptInterface(jsInterface, "GBjs"); + instance.loadUrl("file:///android_asset/app_config/configure.html?rand=" + Math.random() * 500); + } + }); + } + + public static void appMessage(GBDeviceEventAppMessage message) { + + if (instance == null) { + LOG.warn("WEBVIEW is not initialized, cannot send appMessages to it"); + return; + } + + final String appMessage = parseIncomingAppMessage(message.message, message.appUUID); + LOG.debug("to WEBVIEW: " + appMessage); + + webViewSingleton.contextWrapper.runOnUiThread(new Runnable() { + @Override + public void run() { + instance.evaluateJavascript("Pebble.evaluate('appmessage',[" + appMessage + "]);", new ValueCallback() { + @Override + public void onReceiveValue(String s) { + //TODO: the message should be acked here instead of in PebbleIoThread + LOG.debug("Callback from appmessage: " + s); + } + }); } }); } public static void disposeWebView() { - new Handler(Looper.getMainLooper()).post(new Runnable() { + webViewSingleton.contextWrapper.runOnUiThread(new Runnable() { @Override public void run() { if (instance != null) { @@ -154,7 +147,7 @@ public class WebViewSingleton { // instance.destroy(); // instance = null; // contextWrapper = null; - jsInterface = null; +// jsInterface = null; } } }); @@ -173,9 +166,9 @@ public class WebViewSingleton { this.timestamp = (System.currentTimeMillis() / 1000) - 86400; //let accessor know this value is really old - if (ActivityCompat.checkSelfPermission(contextWrapper, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED && + if (ActivityCompat.checkSelfPermission(webViewSingleton.contextWrapper, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED && prefs.getBoolean("use_updated_location_if_available", false)) { - LocationManager locationManager = (LocationManager) contextWrapper.getSystemService(Context.LOCATION_SERVICE); + LocationManager locationManager = (LocationManager) webViewSingleton.contextWrapper.getSystemService(Context.LOCATION_SERVICE); Criteria criteria = new Criteria(); String provider = locationManager.getBestProvider(criteria, false); if (provider != null) { @@ -197,11 +190,18 @@ public class WebViewSingleton { } } + private static WebResourceResponse mimicKiezelPayResponse() { + return null; + } + private static WebResourceResponse mimicOpenWeatherMapResponse() { WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec(); - if (weatherSpec == null) return null; + if (weatherSpec == null) { + LOG.warn("WEBVIEW - Weather instance is null, cannot update weather"); + return null; + } //location block CurrentPosition currentPosition = new CurrentPosition(); @@ -276,25 +276,26 @@ public class WebViewSingleton { } private static class GBWebClient extends WebViewClient { - @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { - LOG.debug("WEBVIEW shouldInterceptRequest URL" + request.getUrl()); - if (request.getUrl().toString().startsWith("http://api.openweathermap.org") || request.getUrl().toString().startsWith("https://api.openweathermap.org")) { - return mimicOpenWeatherMapResponse(); - } else { - LOG.debug("WEBVIEW request:" + request.getUrl().toString() + " denied"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + LOG.debug("WEBVIEW shouldInterceptRequest URL" + request.getUrl()); + if (request.getUrl().toString().startsWith("http://api.openweathermap.org") || request.getUrl().toString().startsWith("https://api.openweathermap.org")) { + return mimicOpenWeatherMapResponse(); + } else { + LOG.debug("WEBVIEW request:" + request.getUrl().toString() + " not intercepted"); + } } return super.shouldInterceptRequest(view, request); } @Override public WebResourceResponse shouldInterceptRequest(WebView view, String url) { - LOG.debug("WEBVIEW shouldInterceptRequest URL" + url); + LOG.debug("WEBVIEW shouldInterceptRequest URL (legacy)" + url); if (url.startsWith("http://api.openweathermap.org") || url.startsWith("https://api.openweathermap.org")) { return mimicOpenWeatherMapResponse(); } else { - LOG.debug("WEBVIEW request:" + url + " denied"); + LOG.debug("WEBVIEW request:" + url + " not intercepted"); } return super.shouldInterceptRequest(view, url); } @@ -304,7 +305,7 @@ public class WebViewSingleton { if (url.startsWith("http://") || url.startsWith("https://")) { Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); - contextWrapper.startActivity(i); + webViewSingleton.contextWrapper.startActivity(i); } else { url = url.replaceFirst("^pebblejs://close#", "file:///android_asset/app_config/configure.html?config=true&json="); view.loadUrl(url); @@ -314,7 +315,6 @@ public class WebViewSingleton { } } - private static JSONObject getAppConfigurationKeys(UUID uuid) { try { File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); @@ -330,6 +330,48 @@ public class WebViewSingleton { return null; } + public static String parseIncomingAppMessage(String msg, UUID uuid) { + JSONObject jsAppMessage = new JSONObject(); + + JSONObject knownKeys = getAppConfigurationKeys(uuid); + HashMap appKeysMap = new HashMap(); + + String inKey, outKey; + //knownKeys contains "name"->"index", we need to reverse that + for (Iterator key = knownKeys.keys(); key.hasNext(); ) { + inKey = key.next(); + appKeysMap.put(knownKeys.optInt(inKey), inKey); + } + + try { + JSONArray incoming = new JSONArray(msg); + JSONObject outgoing = new JSONObject(); + for (int i = 0; i < incoming.length(); i++) { + JSONObject in = incoming.getJSONObject(i); + outKey = null; + Object outValue = null; + for (Iterator key = in.keys(); key.hasNext(); ) { + inKey = key.next(); + switch (inKey) { + case "key": + outKey = appKeysMap.get(in.optInt(inKey)); + break; + case "value": + outValue = in.get(inKey); + } + } + if (outKey != null && outValue != null) { + outgoing.put(outKey, outValue); + } + } + jsAppMessage.put("payload", outgoing); + + } catch (JSONException e) { + e.printStackTrace(); + } + return jsAppMessage.toString(); + } + private static class JSInterface { UUID mUuid; @@ -341,48 +383,6 @@ public class WebViewSingleton { this.mUuid = mUuid; } - public String parseIncomingAppMessage(String msg) { - JSONObject jsAppMessage = new JSONObject(); - - JSONObject knownKeys = getAppConfigurationKeys(this.mUuid); - HashMap appKeysMap = new HashMap(); - - String inKey, outKey; - //knownKeys contains "name"->"index", we need to reverse that - for (Iterator key = knownKeys.keys(); key.hasNext(); ) { - inKey = key.next(); - appKeysMap.put(knownKeys.optInt(inKey), inKey); - } - - try { - JSONArray incoming = new JSONArray(msg); - JSONObject outgoing = new JSONObject(); - for (int i = 0; i < incoming.length(); i++) { - JSONObject in = incoming.getJSONObject(i); - outKey = null; - Object outValue = null; - for (Iterator key = in.keys(); key.hasNext(); ) { - inKey = key.next(); - switch (inKey) { - case "key": - outKey = appKeysMap.get(in.optInt(inKey)); - break; - case "value": - outValue = in.get(inKey); - } - } - if (outKey != null && outValue != null) { - outgoing.put(outKey, outValue); - } - } - jsAppMessage.put("payload", outgoing); - - } catch (JSONException e) { - e.printStackTrace(); - } - return jsAppMessage.toString(); - } - private boolean isLocationEnabledForWatchApp() { return true; //as long as we don't give watchapp internet access it's not a problem @@ -390,7 +390,7 @@ public class WebViewSingleton { @JavascriptInterface public void gbLog(String msg) { - LOG.debug("WEBVIEW", msg); + LOG.debug("WEBVIEW webpage log", msg); } @JavascriptInterface @@ -457,6 +457,7 @@ public class WebViewSingleton { @JavascriptInterface public String getAppConfigurationFile() { + LOG.debug("WEBVIEW loading config file of " + this.mUuid.toString()); try { File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); File configurationFile = new File(destDir, this.mUuid.toString() + "_config.js");