From 76be0ae676e86ffccae74408b7e681d63747e0c4 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Tue, 28 Feb 2017 21:11:26 +0100 Subject: [PATCH] Pebble background webview improvements: remove the pre-KITKAT checks as the minsupportedversion is KITKAT fix the currentPosition override move the returned configuration parsing to onResume method (where it was previously) as it seems more reliable pass the whole GBDeviceEventAppMessage object, this way the jsInterface in the webview doesn't need to be static anymore change the way the webview is initialized, this way the context doesn't need to be static anymore use runOnUiThread instead of the Handler .. MainLooper to post the commands to the webview instance comment a couple of custom message handler for weather as the watchapp seem to work well with JS alone add a couple of log messages for debugging known issue: legacy app configuration pasting does not work. --- .../app_config/js/gadgetbridge_boilerplate.js | 4 +- .../activities/ExternalPebbleJSActivity.java | 56 ++--- .../devices/pebble/PebbleIoThread.java | 3 +- .../devices/pebble/PebbleProtocol.java | 12 +- .../gadgetbridge/util/WebViewSingleton.java | 207 +++++++++--------- 5 files changed, 134 insertions(+), 148 deletions(-) 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");