1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-08-25 08:40:43 +02:00

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.
This commit is contained in:
Daniele Gobbetti 2017-02-28 21:11:26 +01:00
parent 593b169f00
commit 76be0ae676
5 changed files with 134 additions and 148 deletions

View File

@ -1,7 +1,5 @@
navigator.geolocation.getCurrentPosition = function(success, failure) { //override because default implementation requires GPS permission navigator.geolocation.getCurrentPosition = function(success, failure) { //override because default implementation requires GPS permission
success(JSON.parse(GBjs.getCurrentPosition())); success(JSON.parse(GBjs.getCurrentPosition()))
failure({ code: 2, message: "POSITION_UNAVAILABLE"});
} }
if (window.Storage){ if (window.Storage){

View File

@ -3,7 +3,6 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.NavUtils; import android.support.v4.app.NavUtils;
import android.view.MenuItem; import android.view.MenuItem;
@ -61,41 +60,14 @@ public class ExternalPebbleJSActivity extends GBActivity {
@Override @Override
public void onViewAttachedToWindow(View v) { 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); 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 {
//show configuration //show configuration
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
myWebView.evaluateJavascript("Pebble.evaluate('showConfiguration');", new ValueCallback<String>() { myWebView.evaluateJavascript("Pebble.evaluate('showConfiguration');", new ValueCallback<String>() {
@Override @Override
public void onReceiveValue(String s) { public void onReceiveValue(String s) {
LOG.debug("Callback from showConfiguration", s); LOG.debug("Callback from showConfiguration: " + s);
} }
}); });
} else {
((WebView) v).loadUrl("javascript:Pebble.evaluate('showConfiguration');");
}
}
} }
@Override @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 @Override
protected void onNewIntent(Intent incoming) { protected void onNewIntent(Intent incoming) {
incoming.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); incoming.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

View File

@ -82,8 +82,7 @@ class PebbleIoThread extends GBDeviceIoThread {
private int mBytesWritten = -1; private int mBytesWritten = -1;
private void sendAppMessageJS(GBDeviceEventAppMessage appMessage) { private void sendAppMessageJS(GBDeviceEventAppMessage appMessage) {
// WebViewSingleton.runJavascriptInterface(gbDevice, appMessage.appUUID); WebViewSingleton.appMessage(appMessage);
WebViewSingleton.appMessage(appMessage.message);
write(mPebbleProtocol.encodeApplicationMessageAck(appMessage.appUUID, (byte) appMessage.id)); write(mPebbleProtocol.encodeApplicationMessageAck(appMessage.appUUID, (byte) appMessage.id));
} }

View File

@ -385,16 +385,16 @@ public class PebbleProtocol extends GBDeviceProtocol {
super(device); super(device);
mAppMessageHandlers.put(UUID_MORPHEUZ, new AppMessageHandlerMorpheuz(UUID_MORPHEUZ, PebbleProtocol.this)); mAppMessageHandlers.put(UUID_MORPHEUZ, new AppMessageHandlerMorpheuz(UUID_MORPHEUZ, PebbleProtocol.this));
mAppMessageHandlers.put(UUID_MISFIT, new AppMessageHandlerMisfit(UUID_MISFIT, 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_PEBSTYLE, new AppMessageHandlerPebStyle(UUID_PEBSTYLE, PebbleProtocol.this));
//mAppMessageHandlers.put(UUID_MARIOTIME, new AppMessageHandlerMarioTime(UUID_MARIOTIME, 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_HELTHIFY, new AppMessageHandlerHealthify(UUID_HELTHIFY, PebbleProtocol.this));
mAppMessageHandlers.put(UUID_TREKVOLLE, new AppMessageHandlerTrekVolle(UUID_TREKVOLLE, 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_SQUARE, new AppMessageHandlerSquare(UUID_SQUARE, PebbleProtocol.this));
mAppMessageHandlers.put(UUID_ZALEWSZCZAK_CROWEX, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_CROWEX, 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_FANCY, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_FANCY, PebbleProtocol.this));
mAppMessageHandlers.put(UUID_ZALEWSZCZAK_TALLY, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_TALLY, 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_OBSIDIAN, new AppMessageHandlerObsidian(UUID_OBSIDIAN, PebbleProtocol.this));
} }
private final HashMap<Byte, DatalogSession> mDatalogSessions = new HashMap<>(); private final HashMap<Byte, DatalogSession> mDatalogSessions = new HashMap<>();

View File

@ -1,19 +1,15 @@
package nodomain.freeyourgadget.gadgetbridge.util; package nodomain.freeyourgadget.gadgetbridge.util;
import android.Manifest; import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.MutableContextWrapper;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.location.Criteria; import android.location.Criteria;
import android.location.Location; import android.location.Location;
import android.location.LocationManager; import android.location.LocationManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat; import android.support.v4.app.ActivityCompat;
import android.webkit.ConsoleMessage; import android.webkit.ConsoleMessage;
@ -52,6 +48,7 @@ import java.util.Scanner;
import java.util.UUID; import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Weather; import nodomain.freeyourgadget.gadgetbridge.model.Weather;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
@ -61,16 +58,16 @@ public class WebViewSingleton {
private static final Logger LOG = LoggerFactory.getLogger(WebViewSingleton.class); private static final Logger LOG = LoggerFactory.getLogger(WebViewSingleton.class);
private static WebView instance = null; private static WebView instance = null;
private static JSInterface jsInterface; private Activity contextWrapper;
private static MutableContextWrapper contextWrapper = null; private static WebViewSingleton webViewSingleton = new WebViewSingleton();
private WebViewSingleton() { private WebViewSingleton() {
} }
public static synchronized WebView createWebView(Context context) { public static synchronized WebView createWebView(Activity context) {
if (instance == null) { if (instance == null) {
contextWrapper = new MutableContextWrapper(context); webViewSingleton.contextWrapper = context;
instance = new WebView(contextWrapper); instance = new WebView(context);
instance.setWillNotDraw(true); instance.setWillNotDraw(true);
instance.clearCache(true); instance.clearCache(true);
instance.setWebViewClient(new GBWebClient()); instance.setWebViewClient(new GBWebClient());
@ -86,9 +83,9 @@ public class WebViewSingleton {
return instance; return instance;
} }
public static void updateActivityContext(Context context) { public static void updateActivityContext(Activity context) {
if (context instanceof Activity) { if (context instanceof Activity) {
contextWrapper.setBaseContext(context); webViewSingleton.contextWrapper = context;
} }
} }
@ -97,50 +94,46 @@ public class WebViewSingleton {
return instance; return instance;
} }
public static void runJavascriptInterface(final GBDevice device, final UUID uuid) { public static void runJavascriptInterface(GBDevice device, UUID uuid) {
if (jsInterface == null || !device.equals(jsInterface.device) || !uuid.equals(jsInterface.mUuid)) {
jsInterface = new JSInterface(device, uuid); final JSInterface jsInterface = new JSInterface(device, uuid);
new Handler(Looper.getMainLooper()).post(new Runnable() {
webViewSingleton.contextWrapper.runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
instance.removeJavascriptInterface("GBjs"); instance.removeJavascriptInterface("GBjs");
instance.addJavascriptInterface(jsInterface, "GBjs"); instance.addJavascriptInterface(jsInterface, "GBjs");
instance.loadUrl("file:///android_asset/app_config/configure.html"); instance.loadUrl("file:///android_asset/app_config/configure.html?rand=" + Math.random() * 500);
} }
}); });
} else {
LOG.debug("Not replacing the JS in the webview. JS uuid " + jsInterface.mUuid.toString());
}
} }
public static void appMessage(String message) { public static void appMessage(GBDeviceEventAppMessage message) {
if (instance == null) if (instance == null) {
LOG.warn("WEBVIEW is not initialized, cannot send appMessages to it");
return; return;
}
final String appMessage = jsInterface.parseIncomingAppMessage(message); final String appMessage = parseIncomingAppMessage(message.message, message.appUUID);
LOG.debug("to WEBVIEW: " + appMessage); LOG.debug("to WEBVIEW: " + appMessage);
new Handler(Looper.getMainLooper()).post(new Runnable() { webViewSingleton.contextWrapper.runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
instance.evaluateJavascript("Pebble.evaluate('appmessage',[" + appMessage + "]);", new ValueCallback<String>() { instance.evaluateJavascript("Pebble.evaluate('appmessage',[" + appMessage + "]);", new ValueCallback<String>() {
@Override @Override
public void onReceiveValue(String s) { public void onReceiveValue(String s) {
//TODO: the message should be acked here instead of in PebbleIoThread //TODO: the message should be acked here instead of in PebbleIoThread
LOG.debug("Callback from appmessage", s); LOG.debug("Callback from appmessage: " + s);
} }
}); });
} else {
instance.loadUrl("javascript:Pebble.evaluate('appmessage',[" + appMessage + "]);");
}
} }
}); });
} }
public static void disposeWebView() { public static void disposeWebView() {
new Handler(Looper.getMainLooper()).post(new Runnable() { webViewSingleton.contextWrapper.runOnUiThread(new Runnable() {
@Override @Override
public void run() { public void run() {
if (instance != null) { if (instance != null) {
@ -154,7 +147,7 @@ public class WebViewSingleton {
// instance.destroy(); // instance.destroy();
// instance = null; // instance = null;
// contextWrapper = 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 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)) { 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(); Criteria criteria = new Criteria();
String provider = locationManager.getBestProvider(criteria, false); String provider = locationManager.getBestProvider(criteria, false);
if (provider != null) { if (provider != null) {
@ -197,11 +190,18 @@ public class WebViewSingleton {
} }
} }
private static WebResourceResponse mimicKiezelPayResponse() {
return null;
}
private static WebResourceResponse mimicOpenWeatherMapResponse() { private static WebResourceResponse mimicOpenWeatherMapResponse() {
WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec(); 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 //location block
CurrentPosition currentPosition = new CurrentPosition(); CurrentPosition currentPosition = new CurrentPosition();
@ -276,25 +276,26 @@ public class WebViewSingleton {
} }
private static class GBWebClient extends WebViewClient { private static class GBWebClient extends WebViewClient {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override @Override
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
LOG.debug("WEBVIEW shouldInterceptRequest URL" + request.getUrl()); LOG.debug("WEBVIEW shouldInterceptRequest URL" + request.getUrl());
if (request.getUrl().toString().startsWith("http://api.openweathermap.org") || request.getUrl().toString().startsWith("https://api.openweathermap.org")) { if (request.getUrl().toString().startsWith("http://api.openweathermap.org") || request.getUrl().toString().startsWith("https://api.openweathermap.org")) {
return mimicOpenWeatherMapResponse(); return mimicOpenWeatherMapResponse();
} else { } else {
LOG.debug("WEBVIEW request:" + request.getUrl().toString() + " denied"); LOG.debug("WEBVIEW request:" + request.getUrl().toString() + " not intercepted");
}
} }
return super.shouldInterceptRequest(view, request); return super.shouldInterceptRequest(view, request);
} }
@Override @Override
public WebResourceResponse shouldInterceptRequest(WebView view, String url) { 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")) { if (url.startsWith("http://api.openweathermap.org") || url.startsWith("https://api.openweathermap.org")) {
return mimicOpenWeatherMapResponse(); return mimicOpenWeatherMapResponse();
} else { } else {
LOG.debug("WEBVIEW request:" + url + " denied"); LOG.debug("WEBVIEW request:" + url + " not intercepted");
} }
return super.shouldInterceptRequest(view, url); return super.shouldInterceptRequest(view, url);
} }
@ -304,7 +305,7 @@ public class WebViewSingleton {
if (url.startsWith("http://") || url.startsWith("https://")) { if (url.startsWith("http://") || url.startsWith("https://")) {
Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
contextWrapper.startActivity(i); webViewSingleton.contextWrapper.startActivity(i);
} else { } else {
url = url.replaceFirst("^pebblejs://close#", "file:///android_asset/app_config/configure.html?config=true&json="); url = url.replaceFirst("^pebblejs://close#", "file:///android_asset/app_config/configure.html?config=true&json=");
view.loadUrl(url); view.loadUrl(url);
@ -314,7 +315,6 @@ public class WebViewSingleton {
} }
} }
private static JSONObject getAppConfigurationKeys(UUID uuid) { private static JSONObject getAppConfigurationKeys(UUID uuid) {
try { try {
File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache");
@ -330,21 +330,10 @@ public class WebViewSingleton {
return null; return null;
} }
private static class JSInterface { public static String parseIncomingAppMessage(String msg, UUID uuid) {
UUID mUuid;
GBDevice device;
public JSInterface(GBDevice device, UUID mUuid) {
LOG.debug("Creating JS interface for UUID: " + mUuid.toString());
this.device = device;
this.mUuid = mUuid;
}
public String parseIncomingAppMessage(String msg) {
JSONObject jsAppMessage = new JSONObject(); JSONObject jsAppMessage = new JSONObject();
JSONObject knownKeys = getAppConfigurationKeys(this.mUuid); JSONObject knownKeys = getAppConfigurationKeys(uuid);
HashMap<Integer, String> appKeysMap = new HashMap<Integer, String>(); HashMap<Integer, String> appKeysMap = new HashMap<Integer, String>();
String inKey, outKey; String inKey, outKey;
@ -383,6 +372,17 @@ public class WebViewSingleton {
return jsAppMessage.toString(); return jsAppMessage.toString();
} }
private static class JSInterface {
UUID mUuid;
GBDevice device;
public JSInterface(GBDevice device, UUID mUuid) {
LOG.debug("Creating JS interface for UUID: " + mUuid.toString());
this.device = device;
this.mUuid = mUuid;
}
private boolean isLocationEnabledForWatchApp() { private boolean isLocationEnabledForWatchApp() {
return true; //as long as we don't give watchapp internet access it's not a problem 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 @JavascriptInterface
public void gbLog(String msg) { public void gbLog(String msg) {
LOG.debug("WEBVIEW", msg); LOG.debug("WEBVIEW webpage log", msg);
} }
@JavascriptInterface @JavascriptInterface
@ -457,6 +457,7 @@ public class WebViewSingleton {
@JavascriptInterface @JavascriptInterface
public String getAppConfigurationFile() { public String getAppConfigurationFile() {
LOG.debug("WEBVIEW loading config file of " + this.mUuid.toString());
try { try {
File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache");
File configurationFile = new File(destDir, this.mUuid.toString() + "_config.js"); File configurationFile = new File(destDir, this.mUuid.toString() + "_config.js");