From 53d8f8867014e96862dbfc3084d31a07b2afa3ed Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Wed, 28 Dec 2016 20:53:17 +0100 Subject: [PATCH 01/48] Pebble: first steps towards background JS execution. Progress so far: - webview is created upon watchapp launch - webview is destroyed after disconnect - ready event is fired in the background - showConfiguration is fired upon webview display --- .../app_config/js/gadgetbridge_boilerplate.js | 6 +- .../activities/ExternalPebbleJSActivity.java | 278 +++------------- .../devices/pebble/PebbleIoThread.java | 5 + .../gadgetbridge/util/WebViewSingleton.java | 301 ++++++++++++++++++ .../layout/activity_external_pebble_js.xml | 8 +- 5 files changed, 359 insertions(+), 239 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java 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 caf39669f..3495b0c58 100644 --- a/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js +++ b/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js @@ -104,7 +104,7 @@ function gbPebble() { this.actuallySendData = function() { GBjs.sendAppMessage(self.configurationValues); - GBjs.closeActivity(); + GBActivity.closeActivity(); } this.savePreset = function() { @@ -169,7 +169,7 @@ function gbPebble() { this.showConfiguration = function() { console.error("This watchapp doesn't support configuration"); - GBjs.closeActivity(); + GBActivity.closeActivity(); } this.parseReturnedPebbleJS = function() { @@ -204,7 +204,6 @@ if (jsConfigFile != null) { if (json_string != '') { Pebble.evaluate('webviewclosed',[t]); } - } else { if (storedPreset === undefined) { var presetElements = document.getElementsByClassName("load_presets"); @@ -212,7 +211,6 @@ if (jsConfigFile != null) { presetElements[i].style.display = 'none'; } } - Pebble.evaluate('showConfiguration'); } }); } 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 856fe0ad1..69fdab7d0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java @@ -3,42 +3,26 @@ 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.util.Log; import android.view.MenuItem; -import android.webkit.ConsoleMessage; import android.webkit.JavascriptInterface; -import android.webkit.WebChromeClient; -import android.webkit.WebSettings; +import android.webkit.ValueCallback; import android.webkit.WebView; -import android.webkit.WebViewClient; +import android.widget.FrameLayout; import android.widget.Toast; -import org.json.JSONException; -import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.io.Writer; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Iterator; -import java.util.Scanner; import java.util.UUID; -import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; -import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; -import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils; +import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton; public class ExternalPebbleJSActivity extends GBActivity { @@ -64,21 +48,8 @@ public class ExternalPebbleJSActivity extends GBActivity { setContentView(R.layout.activity_external_pebble_js); - myWebView = (WebView) findViewById(R.id.configureWebview); - myWebView.clearCache(true); - myWebView.setWebViewClient(new GBWebClient()); - myWebView.setWebChromeClient(new GBChromeClient()); - WebSettings webSettings = myWebView.getSettings(); - webSettings.setJavaScriptEnabled(true); - //needed to access the DOM - webSettings.setDomStorageEnabled(true); - //needed for localstorage - webSettings.setDatabaseEnabled(true); - JSInterface gbJSInterface = new JSInterface(this); - myWebView.addJavascriptInterface(gbJSInterface, "GBjs"); - - myWebView.loadUrl("file:///android_asset/app_config/configure.html"); + WebViewSingleton.getorInitWebView(this, mGBDevice, appUuid); } @@ -94,6 +65,19 @@ public class ExternalPebbleJSActivity extends GBActivity { super.onResume(); String queryString = ""; + myWebView = WebViewSingleton.getorInitWebView(this, mGBDevice, appUuid); + myWebView.addJavascriptInterface(new ActivityJSInterface(this), "GBActivity"); + + + FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder); + if (myWebView != null) + fl.addView(myWebView); + + //needed to display clay dialogs + myWebView.getSettings().setUseWideViewPort(true); + myWebView.requestFocus(); + + if (confUri != null) { //getting back with configuration data try { @@ -103,209 +87,28 @@ public class ExternalPebbleJSActivity extends GBActivity { GB.toast("returned uri: " + confUri.toString(), Toast.LENGTH_LONG, GB.ERROR); } myWebView.loadUrl("file:///android_asset/app_config/configure.html?" + queryString); - } - - } - - private JSONObject getAppConfigurationKeys() { - try { - File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); - File configurationFile = new File(destDir, appUuid.toString() + ".json"); - if (configurationFile.exists()) { - String jsonstring = FileUtils.getStringFromFile(configurationFile); - JSONObject json = new JSONObject(jsonstring); - return json.getJSONObject("appKeys"); - } - } catch (IOException | JSONException e) { - e.printStackTrace(); - } - return null; - } - - private class GBChromeClient extends WebChromeClient { - @Override - public boolean onConsoleMessage(ConsoleMessage consoleMessage) { - if (ConsoleMessage.MessageLevel.ERROR.equals(consoleMessage.messageLevel())) { - GB.toast(consoleMessage.message(), Toast.LENGTH_LONG, GB.ERROR); - //TODO: show error page - } - return super.onConsoleMessage(consoleMessage); - } - - } - - private class GBWebClient extends WebViewClient { - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - 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); - startActivity(i); + } else { + //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 { - url = url.replaceFirst("^pebblejs://close#", "file:///android_asset/app_config/configure.html?config=true&json="); - view.loadUrl(url); + myWebView.loadUrl("javascript:Pebble.evaluate('showConfiguration');"); } - - return true; - } + } - private class JSInterface { + @Override + protected void onPause() { - Context mContext; - - public JSInterface(Context c) { - mContext = c; - } - - @JavascriptInterface - public void gbLog(String msg) { - Log.d("WEBVIEW", msg); - } - - @JavascriptInterface - public void sendAppMessage(String msg) { - LOG.debug("from WEBVIEW: " + msg); - JSONObject knownKeys = getAppConfigurationKeys(); - - try { - JSONObject in = new JSONObject(msg); - JSONObject out = new JSONObject(); - String inKey, outKey; - boolean passKey; - for (Iterator key = in.keys(); key.hasNext(); ) { - passKey = false; - inKey = key.next(); - outKey = null; - int pebbleAppIndex = knownKeys.optInt(inKey, -1); - if (pebbleAppIndex != -1) { - passKey = true; - outKey = String.valueOf(pebbleAppIndex); - } else { - //do not discard integer keys (see https://developer.pebble.com/guides/communication/using-pebblekit-js/ ) - Scanner scanner = new Scanner(inKey); - if (scanner.hasNextInt() && inKey.equals("" + scanner.nextInt())) { - passKey = true; - outKey = inKey; - } - } - - if (passKey) { - Object obj = in.get(inKey); - if (obj instanceof Boolean) { - obj = ((Boolean) obj) ? "true" : "false"; - } - out.put(outKey, obj); - } else { - GB.toast("Discarded key " + inKey + ", not found in the local configuration and is not an integer key.", Toast.LENGTH_SHORT, GB.WARN); - } - - } - LOG.info(out.toString()); - GBApplication.deviceService().onAppConfiguration(appUuid, out.toString()); - - } catch (JSONException e) { - e.printStackTrace(); - } - } - - @JavascriptInterface - public String getActiveWatchInfo() { - JSONObject wi = new JSONObject(); - try { - wi.put("firmware", mGBDevice.getFirmwareVersion()); - wi.put("platform", PebbleUtils.getPlatformName(mGBDevice.getModel())); - wi.put("model", PebbleUtils.getModel(mGBDevice.getModel())); - //TODO: use real info - wi.put("language", "en"); - } catch (JSONException e) { - e.printStackTrace(); - } - //Json not supported apparently, we need to cast back and forth - return wi.toString(); - } - - @JavascriptInterface - public String getAppConfigurationFile() { - try { - File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); - File configurationFile = new File(destDir, appUuid.toString() + "_config.js"); - if (configurationFile.exists()) { - return "file:///" + configurationFile.getAbsolutePath(); - } - } catch (IOException e) { - e.printStackTrace(); - } - return null; - } - - @JavascriptInterface - public String getAppStoredPreset() { - try { - File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); - File configurationFile = new File(destDir, appUuid.toString() + "_preset.json"); - if (configurationFile.exists()) { - return FileUtils.getStringFromFile(configurationFile); - } - } catch (IOException e) { - GB.toast("Error reading presets", Toast.LENGTH_LONG, GB.ERROR); - e.printStackTrace(); - } - return null; - } - - @JavascriptInterface - public void saveAppStoredPreset(String msg) { - Writer writer; - - try { - File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); - File presetsFile = new File(destDir, appUuid.toString() + "_preset.json"); - writer = new BufferedWriter(new FileWriter(presetsFile)); - writer.write(msg); - writer.close(); - GB.toast("Presets stored", Toast.LENGTH_SHORT, GB.INFO); - } catch (IOException e) { - GB.toast("Error storing presets", Toast.LENGTH_LONG, GB.ERROR); - e.printStackTrace(); - } - } - - @JavascriptInterface - public String getAppUUID() { - return appUuid.toString(); - } - - @JavascriptInterface - public String getAppLocalstoragePrefix() { - String prefix = mGBDevice.getAddress() + appUuid.toString(); - try { - MessageDigest digest = MessageDigest.getInstance("SHA-1"); - byte[] bytes = prefix.getBytes("UTF-8"); - digest.update(bytes, 0, bytes.length); - bytes = digest.digest(); - final StringBuilder sb = new StringBuilder(); - for (int i = 0; i < bytes.length; i++) { - sb.append(String.format("%02X", bytes[i])); - } - return sb.toString().toLowerCase(); - } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { - e.printStackTrace(); - return prefix; - } - } - - @JavascriptInterface - public String getWatchToken() { - //specification says: A string that is guaranteed to be identical for each Pebble device for the same app across different mobile devices. The token is unique to your app and cannot be used to track Pebble devices across applications. see https://developer.pebble.com/docs/js/Pebble/ - return "gb" + appUuid.toString(); - } - - @JavascriptInterface - public void closeActivity() { - NavUtils.navigateUpFromSameTask((ExternalPebbleJSActivity) mContext); - } + FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder); + fl.removeAllViews(); + super.onPause(); } @Override @@ -318,5 +121,18 @@ public class ExternalPebbleJSActivity extends GBActivity { return super.onOptionsItemSelected(item); } + private class ActivityJSInterface { + + Context mContext; + + public ActivityJSInterface(Context c) { + mContext = c; + } + + @JavascriptInterface + public void closeActivity() { + NavUtils.navigateUpFromSameTask((ExternalPebbleJSActivity) mContext); + } + } } 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 bd990d2d6..70ece5f29 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 @@ -49,6 +49,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; +import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton; class PebbleIoThread extends GBDeviceIoThread { private static final Logger LOG = LoggerFactory.getLogger(PebbleIoThread.class); @@ -429,6 +430,9 @@ class PebbleIoThread extends GBDeviceIoThread { } else { gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT); } + + WebViewSingleton.disposeWebView(); + gbDevice.sendDeviceUpdateIntent(getContext()); } @@ -565,6 +569,7 @@ class PebbleIoThread extends GBDeviceIoThread { break; case START: LOG.info("got GBDeviceEventAppManagement START event for uuid: " + appMgmt.uuid); + WebViewSingleton.getorInitWebView(getContext(), gbDevice, appMgmt.uuid); break; default: break; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java new file mode 100644 index 000000000..dc410194b --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -0,0 +1,301 @@ +package nodomain.freeyourgadget.gadgetbridge.util; + +import android.content.Context; +import android.content.Intent; +import android.content.MutableContextWrapper; +import android.net.Uri; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.webkit.ConsoleMessage; +import android.webkit.JavascriptInterface; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.Toast; + +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Iterator; +import java.util.Scanner; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; + +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 WebViewSingleton() { + } + + public static WebView getorInitWebView(final Context context, final GBDevice device, final UUID uuid) { + final MutableContextWrapper _contextWrapper = new MutableContextWrapper(context); + if (contextWrapper != null) { + contextWrapper.setBaseContext(context); + } else { + contextWrapper = _contextWrapper; + } + + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + if (instance == null) { + instance = new WebView(_contextWrapper); + instance.clearCache(true); + instance.setWebViewClient(new GBWebClient()); + instance.setWebChromeClient(new GBChromeClient()); + instance.setWebContentsDebuggingEnabled(true); + WebSettings webSettings = instance.getSettings(); + webSettings.setJavaScriptEnabled(true); + //needed to access the DOM + webSettings.setDomStorageEnabled(true); + //needed for localstorage + webSettings.setDatabaseEnabled(true); + } + + if (jsInterface == null || (jsInterface != null && (!device.equals(jsInterface.device) || !uuid.equals(jsInterface.mUuid)))) { + instance.removeJavascriptInterface("GBjs"); + jsInterface = new JSInterface(device, uuid); //TODO: context is used for navigating up but this only works for the externalJSActivity + instance.addJavascriptInterface(jsInterface, "GBjs"); + instance.loadUrl("file:///android_asset/app_config/configure.html"); + } else { + LOG.debug("Not reloading the webview " + jsInterface.mUuid.toString()); + } + } + }); + + return instance; + } + + + public static void disposeWebView() { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + if (instance != null) { + instance.destroy(); + instance = null; + } + } + }); + } + + private static class GBChromeClient extends WebChromeClient { + @Override + public boolean onConsoleMessage(ConsoleMessage consoleMessage) { + if (ConsoleMessage.MessageLevel.ERROR.equals(consoleMessage.messageLevel())) { + GB.toast(consoleMessage.message(), Toast.LENGTH_LONG, GB.ERROR); + //TODO: show error page + } + return super.onConsoleMessage(consoleMessage); + } + + } + + private static class GBWebClient extends WebViewClient { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + 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); + } else { + url = url.replaceFirst("^pebblejs://close#", "file:///android_asset/app_config/configure.html?config=true&json="); + view.loadUrl(url); + } + + return true; + } + } + + + private static JSONObject getAppConfigurationKeys(UUID uuid) { + try { + File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); + File configurationFile = new File(destDir, uuid.toString() + ".json"); + if (configurationFile.exists()) { + String jsonstring = FileUtils.getStringFromFile(configurationFile); + JSONObject json = new JSONObject(jsonstring); + return json.getJSONObject("appKeys"); + } + } catch (IOException | JSONException e) { + e.printStackTrace(); + } + return null; + } + + private static class JSInterface { + + UUID mUuid; + GBDevice device; + + public JSInterface(GBDevice device, UUID mUuid) { + LOG.debug("Creating JS interface"); + this.device = device; + this.mUuid = mUuid; + } + + @JavascriptInterface + public void gbLog(String msg) { + Log.d("WEBVIEW", msg); + } + + @JavascriptInterface + public void sendAppMessage(String msg) { + LOG.debug("from WEBVIEW: " + msg); + JSONObject knownKeys = getAppConfigurationKeys(this.mUuid); + + try { + JSONObject in = new JSONObject(msg); + JSONObject out = new JSONObject(); + String inKey, outKey; + boolean passKey; + for (Iterator key = in.keys(); key.hasNext(); ) { + passKey = false; + inKey = key.next(); + outKey = null; + int pebbleAppIndex = knownKeys.optInt(inKey, -1); + if (pebbleAppIndex != -1) { + passKey = true; + outKey = String.valueOf(pebbleAppIndex); + } else { + //do not discard integer keys (see https://developer.pebble.com/guides/communication/using-pebblekit-js/ ) + Scanner scanner = new Scanner(inKey); + if (scanner.hasNextInt() && inKey.equals("" + scanner.nextInt())) { + passKey = true; + outKey = inKey; + } + } + + if (passKey) { + Object obj = in.get(inKey); + if (obj instanceof Boolean) { + obj = ((Boolean) obj) ? "true" : "false"; + } + out.put(outKey, obj); + } else { + GB.toast("Discarded key " + inKey + ", not found in the local configuration and is not an integer key.", Toast.LENGTH_SHORT, GB.WARN); + } + + } + LOG.info(out.toString()); + GBApplication.deviceService().onAppConfiguration(this.mUuid, out.toString()); + + } catch (JSONException e) { + e.printStackTrace(); + } + } + + @JavascriptInterface + public String getActiveWatchInfo() { + JSONObject wi = new JSONObject(); + try { + wi.put("firmware", device.getFirmwareVersion()); + wi.put("platform", PebbleUtils.getPlatformName(device.getModel())); + wi.put("model", PebbleUtils.getModel(device.getModel())); + //TODO: use real info + wi.put("language", "en"); + } catch (JSONException e) { + e.printStackTrace(); + } + //Json not supported apparently, we need to cast back and forth + return wi.toString(); + } + + @JavascriptInterface + public String getAppConfigurationFile() { + try { + File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); + File configurationFile = new File(destDir, this.mUuid.toString() + "_config.js"); + if (configurationFile.exists()) { + return "file:///" + configurationFile.getAbsolutePath(); + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + + @JavascriptInterface + public String getAppStoredPreset() { + try { + File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); + File configurationFile = new File(destDir, this.mUuid.toString() + "_preset.json"); + if (configurationFile.exists()) { + return FileUtils.getStringFromFile(configurationFile); + } + } catch (IOException e) { + GB.toast("Error reading presets", Toast.LENGTH_LONG, GB.ERROR); + e.printStackTrace(); + } + return null; + } + + @JavascriptInterface + public void saveAppStoredPreset(String msg) { + Writer writer; + + try { + File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); + File presetsFile = new File(destDir, this.mUuid.toString() + "_preset.json"); + writer = new BufferedWriter(new FileWriter(presetsFile)); + writer.write(msg); + writer.close(); + GB.toast("Presets stored", Toast.LENGTH_SHORT, GB.INFO); + } catch (IOException e) { + GB.toast("Error storing presets", Toast.LENGTH_LONG, GB.ERROR); + e.printStackTrace(); + } + } + + @JavascriptInterface + public String getAppUUID() { + return this.mUuid.toString(); + } + + @JavascriptInterface + public String getAppLocalstoragePrefix() { + String prefix = device.getAddress() + this.mUuid.toString(); + try { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + byte[] bytes = prefix.getBytes("UTF-8"); + digest.update(bytes, 0, bytes.length); + bytes = digest.digest(); + final StringBuilder sb = new StringBuilder(); + for (int i = 0; i < bytes.length; i++) { + sb.append(String.format("%02X", bytes[i])); + } + return sb.toString().toLowerCase(); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { + e.printStackTrace(); + return prefix; + } + } + + @JavascriptInterface + public String getWatchToken() { + //specification says: A string that is guaranteed to be identical for each Pebble device for the same app across different mobile devices. The token is unique to your app and cannot be used to track Pebble devices across applications. see https://developer.pebble.com/docs/js/Pebble/ + return "gb" + this.mUuid.toString(); + } + + } + +} diff --git a/app/src/main/res/layout/activity_external_pebble_js.xml b/app/src/main/res/layout/activity_external_pebble_js.xml index 551fb9276..638624aee 100644 --- a/app/src/main/res/layout/activity_external_pebble_js.xml +++ b/app/src/main/res/layout/activity_external_pebble_js.xml @@ -1,5 +1,5 @@ - + From dbf88bab5f512f17ea0f109f0e72cb769d863428 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Thu, 29 Dec 2016 20:44:04 +0100 Subject: [PATCH 02/48] Outdated TODO removed --- .../freeyourgadget/gadgetbridge/util/WebViewSingleton.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 dc410194b..9c6b0ccad 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -73,7 +73,7 @@ public class WebViewSingleton { if (jsInterface == null || (jsInterface != null && (!device.equals(jsInterface.device) || !uuid.equals(jsInterface.mUuid)))) { instance.removeJavascriptInterface("GBjs"); - jsInterface = new JSInterface(device, uuid); //TODO: context is used for navigating up but this only works for the externalJSActivity + jsInterface = new JSInterface(device, uuid); instance.addJavascriptInterface(jsInterface, "GBjs"); instance.loadUrl("file:///android_asset/app_config/configure.html"); } else { From eaaa9406378f3b5c100758362cb1ad4adefd6f34 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Sun, 1 Jan 2017 18:33:39 +0100 Subject: [PATCH 03/48] Pebble: refactor the background webview Now native controls seem to work (including datepicker), still the webview is not started upon watchapp start, but when long-pressing each app in the app manager. After the webview is started it will live in the background until device disconnect. --- .../activities/ExternalPebbleJSActivity.java | 109 +++++++++--------- .../AbstractAppManagerFragment.java | 3 + .../devices/pebble/PebbleIoThread.java | 1 + .../gadgetbridge/util/WebViewSingleton.java | 92 +++++++-------- 4 files changed, 103 insertions(+), 102 deletions(-) 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 69fdab7d0..b103d1f95 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java @@ -7,6 +7,7 @@ import android.os.Build; import android.os.Bundle; import android.support.v4.app.NavUtils; import android.view.MenuItem; +import android.view.View; import android.webkit.JavascriptInterface; import android.webkit.ValueCallback; import android.webkit.WebView; @@ -45,11 +46,64 @@ public class ExternalPebbleJSActivity extends GBActivity { throw new IllegalArgumentException("Must provide a device when invoking this activity"); } - setContentView(R.layout.activity_external_pebble_js); + myWebView = WebViewSingleton.getorInitWebView(this, mGBDevice, appUuid); + myWebView.setWillNotDraw(false); + myWebView.addJavascriptInterface(new ActivityJSInterface(ExternalPebbleJSActivity.this), "GBActivity"); + FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder); + fl.addView(myWebView); - WebViewSingleton.getorInitWebView(this, mGBDevice, appUuid); + + myWebView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @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 { + //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');"); + } + } + + + } + + @Override + public void onViewDetachedFromWindow(View v) { + myWebView.removeJavascriptInterface("GBActivity"); + myWebView.setWillNotDraw(true); + FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder); + fl.removeAllViews(); + } + }); } @@ -60,57 +114,6 @@ public class ExternalPebbleJSActivity extends GBActivity { confUri = incoming.getData(); } - @Override - protected void onResume() { - super.onResume(); - String queryString = ""; - - myWebView = WebViewSingleton.getorInitWebView(this, mGBDevice, appUuid); - myWebView.addJavascriptInterface(new ActivityJSInterface(this), "GBActivity"); - - - FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder); - if (myWebView != null) - fl.addView(myWebView); - - //needed to display clay dialogs - myWebView.getSettings().setUseWideViewPort(true); - myWebView.requestFocus(); - - - 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); - } - myWebView.loadUrl("file:///android_asset/app_config/configure.html?" + queryString); - } else { - //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 { - myWebView.loadUrl("javascript:Pebble.evaluate('showConfiguration');"); - } - } - - } - - @Override - protected void onPause() { - - FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder); - fl.removeAllViews(); - super.onPause(); - } - @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java index 3e68b2dfa..07b3679f6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java @@ -39,6 +39,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils; +import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton; public abstract class AbstractAppManagerFragment extends Fragment { @@ -323,6 +324,8 @@ public abstract class AbstractAppManagerFragment extends Fragment { view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); popupMenu.show(); + //TODO: replace with local broadcast on app start + WebViewSingleton.getorInitWebView(getActivity(), mGBDevice, selectedApp.getUUID()); return true; } 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 3b56e17fa..bc3fd2026 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 @@ -570,6 +570,7 @@ class PebbleIoThread extends GBDeviceIoThread { case START: LOG.info("got GBDeviceEventAppManagement START event for uuid: " + appMgmt.uuid); WebViewSingleton.getorInitWebView(getContext(), gbDevice, appMgmt.uuid); + //TODO: the method call above will not work the first time as we need an activity. Either we find a way to have one here, or replace it with a local broadcast break; default: break; 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 9c6b0ccad..d988633ca 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -1,12 +1,10 @@ package nodomain.freeyourgadget.gadgetbridge.util; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.MutableContextWrapper; import android.net.Uri; -import android.os.Handler; -import android.os.Looper; -import android.util.Log; import android.webkit.ConsoleMessage; import android.webkit.JavascriptInterface; import android.webkit.WebChromeClient; @@ -35,7 +33,7 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -public class WebViewSingleton { +public class WebViewSingleton extends Activity { private static final Logger LOG = LoggerFactory.getLogger(WebViewSingleton.class); @@ -46,56 +44,52 @@ public class WebViewSingleton { private WebViewSingleton() { } - public static WebView getorInitWebView(final Context context, final GBDevice device, final UUID uuid) { - final MutableContextWrapper _contextWrapper = new MutableContextWrapper(context); - if (contextWrapper != null) { - contextWrapper.setBaseContext(context); - } else { - contextWrapper = _contextWrapper; - } - - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - if (instance == null) { - instance = new WebView(_contextWrapper); - instance.clearCache(true); - instance.setWebViewClient(new GBWebClient()); - instance.setWebChromeClient(new GBChromeClient()); - instance.setWebContentsDebuggingEnabled(true); - WebSettings webSettings = instance.getSettings(); - webSettings.setJavaScriptEnabled(true); - //needed to access the DOM - webSettings.setDomStorageEnabled(true); - //needed for localstorage - webSettings.setDatabaseEnabled(true); - } - - if (jsInterface == null || (jsInterface != null && (!device.equals(jsInterface.device) || !uuid.equals(jsInterface.mUuid)))) { - instance.removeJavascriptInterface("GBjs"); - jsInterface = new JSInterface(device, uuid); - instance.addJavascriptInterface(jsInterface, "GBjs"); - instance.loadUrl("file:///android_asset/app_config/configure.html"); - } else { - LOG.debug("Not reloading the webview " + jsInterface.mUuid.toString()); - } + public static WebView getorInitWebView(Context context, GBDevice device, UUID uuid) { + if (context instanceof Activity) { + final MutableContextWrapper _contextWrapper = new MutableContextWrapper(context); + if (contextWrapper != null) { + contextWrapper.setBaseContext(context); + } else { + contextWrapper = _contextWrapper; + } + } + //here we are sure that contextWrapper is either null or an activity, hence we run on the main thread + if (contextWrapper != null) { + if (instance == null) { + instance = new WebView(contextWrapper); + instance.setWillNotDraw(true); + instance.clearCache(true); + instance.setWebViewClient(new GBWebClient()); + instance.setWebChromeClient(new GBChromeClient()); + instance.setWebContentsDebuggingEnabled(true); + WebSettings webSettings = instance.getSettings(); + webSettings.setJavaScriptEnabled(true); + //needed to access the DOM + webSettings.setDomStorageEnabled(true); + //needed for localstorage + webSettings.setDatabaseEnabled(true); } - }); + if (jsInterface == null || (jsInterface != null && (!device.equals(jsInterface.device) || !uuid.equals(jsInterface.mUuid)))) { + instance.removeJavascriptInterface("GBjs"); + jsInterface = new JSInterface(device, uuid); + instance.addJavascriptInterface(jsInterface, "GBjs"); + instance.loadUrl("file:///android_asset/app_config/configure.html"); + } else { + LOG.debug("Not reloading the webview " + jsInterface.mUuid.toString()); + } + } else { + LOG.debug("WEBV: not using the passed context, as it is not an activity"); + } return instance; } public static void disposeWebView() { - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - if (instance != null) { - instance.destroy(); - instance = null; - } - } - }); + if (instance != null) { + instance.destroy(); + instance = null; + } } private static class GBChromeClient extends WebChromeClient { @@ -155,7 +149,7 @@ public class WebViewSingleton { @JavascriptInterface public void gbLog(String msg) { - Log.d("WEBVIEW", msg); + LOG.debug("WEBVIEW", msg); } @JavascriptInterface @@ -196,7 +190,7 @@ public class WebViewSingleton { } } - LOG.info(out.toString()); + LOG.info("WEBV:" + out.toString()); GBApplication.deviceService().onAppConfiguration(this.mUuid, out.toString()); } catch (JSONException e) { From d22c78e8f7006136f38aa68e48f86bc8d679382e Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Sun, 1 Jan 2017 18:37:42 +0100 Subject: [PATCH 04/48] Pebble: webview, simplify the code since finals are not required anymore --- .../freeyourgadget/gadgetbridge/util/WebViewSingleton.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 d988633ca..b19586a3f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -46,11 +46,10 @@ public class WebViewSingleton extends Activity { public static WebView getorInitWebView(Context context, GBDevice device, UUID uuid) { if (context instanceof Activity) { - final MutableContextWrapper _contextWrapper = new MutableContextWrapper(context); if (contextWrapper != null) { contextWrapper.setBaseContext(context); } else { - contextWrapper = _contextWrapper; + contextWrapper = new MutableContextWrapper(context); } } //here we are sure that contextWrapper is either null or an activity, hence we run on the main thread From 4ef0415da260d9923063b6b0dba73f9560109812 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Sun, 1 Jan 2017 21:01:58 +0100 Subject: [PATCH 05/48] Pebble: webview. Ensure we are on the main thread for disposing the webview and implement sending messages to the webview. --- .../gadgetbridge/util/WebViewSingleton.java | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) 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 b19586a3f..e8a0f1c3c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -5,8 +5,12 @@ import android.content.Context; import android.content.Intent; import android.content.MutableContextWrapper; import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; import android.webkit.ConsoleMessage; import android.webkit.JavascriptInterface; +import android.webkit.ValueCallback; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; @@ -83,12 +87,36 @@ public class WebViewSingleton extends Activity { return instance; } + public static void appMessage(final String message) { + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + instance.evaluateJavascript("Pebble.evaluate('appmessage',[{'payload':" + message + "}]);", new ValueCallback() { + @Override + public void onReceiveValue(String s) { + LOG.debug("Callback from showConfiguration", s); + } + }); + } else { + instance.loadUrl("javascript:Pebble.evaluate('appmessage',[{'payload':" + message + "}]);"); + } + } + }); + } public static void disposeWebView() { - if (instance != null) { - instance.destroy(); - instance = null; - } + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + if (instance != null) { + instance.destroy(); + instance = null; + contextWrapper = null; + jsInterface = null; + } + } + }); } private static class GBChromeClient extends WebChromeClient { From 7ab74fb11b5cd158f2ae320a36608965bdd3cd8a Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Mon, 2 Jan 2017 12:07:57 +0100 Subject: [PATCH 06/48] Pebble: webview. Use the main thread explicitly again and log XHR requests at least on Lollipop and up --- .../gadgetbridge/util/WebViewSingleton.java | 67 +++++++++++++------ 1 file changed, 45 insertions(+), 22 deletions(-) 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 e8a0f1c3c..d2400c9a3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -12,6 +12,8 @@ import android.webkit.ConsoleMessage; import android.webkit.JavascriptInterface; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; import android.webkit.WebSettings; import android.webkit.WebView; import android.webkit.WebViewClient; @@ -55,35 +57,47 @@ public class WebViewSingleton extends Activity { } else { contextWrapper = new MutableContextWrapper(context); } - } - //here we are sure that contextWrapper is either null or an activity, hence we run on the main thread - if (contextWrapper != null) { + if (instance == null) { instance = new WebView(contextWrapper); - instance.setWillNotDraw(true); - instance.clearCache(true); - instance.setWebViewClient(new GBWebClient()); - instance.setWebChromeClient(new GBChromeClient()); - instance.setWebContentsDebuggingEnabled(true); - WebSettings webSettings = instance.getSettings(); - webSettings.setJavaScriptEnabled(true); - //needed to access the DOM - webSettings.setDomStorageEnabled(true); - //needed for localstorage - webSettings.setDatabaseEnabled(true); + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + instance.setWillNotDraw(true); + instance.clearCache(true); + instance.setWebViewClient(new GBWebClient()); + instance.setWebChromeClient(new GBChromeClient()); + instance.setWebContentsDebuggingEnabled(true); + WebSettings webSettings = instance.getSettings(); + webSettings.setJavaScriptEnabled(true); + //needed to access the DOM + webSettings.setDomStorageEnabled(true); + //needed for localstorage + webSettings.setDatabaseEnabled(true); + } + }); } - if (jsInterface == null || (jsInterface != null && (!device.equals(jsInterface.device) || !uuid.equals(jsInterface.mUuid)))) { - instance.removeJavascriptInterface("GBjs"); - jsInterface = new JSInterface(device, uuid); - instance.addJavascriptInterface(jsInterface, "GBjs"); - instance.loadUrl("file:///android_asset/app_config/configure.html"); - } else { - LOG.debug("Not reloading the webview " + jsInterface.mUuid.toString()); - } } else { LOG.debug("WEBV: not using the passed context, as it is not an activity"); } + + if (jsInterface == null || (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() { + if (instance != null) { + instance.removeJavascriptInterface("GBjs"); + instance.addJavascriptInterface(jsInterface, "GBjs"); + instance.loadUrl("file:///android_asset/app_config/configure.html"); + } + } + }); + } else { + LOG.debug("Not reloading the webview " + jsInterface.mUuid.toString()); + } + return instance; } @@ -132,6 +146,15 @@ public class WebViewSingleton extends Activity { } private static class GBWebClient extends WebViewClient { + @Override + 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 request:" + request.toString()); + return super.shouldInterceptRequest(view, request); + } + @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (url.startsWith("http://") || url.startsWith("https://")) { From 0efacfbad58129a8fdf491905aeba58d5bbd7268 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Mon, 2 Jan 2017 12:32:52 +0100 Subject: [PATCH 07/48] Pebble: webview ensure the webview works no matter which the order of loading the components is. --- .../gadgetbridge/util/WebViewSingleton.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) 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 d2400c9a3..f1a8a58cb 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -74,6 +74,11 @@ public class WebViewSingleton extends Activity { webSettings.setDomStorageEnabled(true); //needed for localstorage webSettings.setDatabaseEnabled(true); + if (jsInterface != null) { + LOG.debug("Attaching the existing jsInterface to the new webview instance"); + instance.addJavascriptInterface(jsInterface, "GBjs"); + instance.loadUrl("file:///android_asset/app_config/configure.html"); + } } }); } @@ -95,7 +100,7 @@ public class WebViewSingleton extends Activity { } }); } else { - LOG.debug("Not reloading the webview " + jsInterface.mUuid.toString()); + LOG.debug("Not replacing the JS in the webview. JS uuid " + jsInterface.mUuid.toString()); } return instance; @@ -192,7 +197,7 @@ public class WebViewSingleton extends Activity { GBDevice device; public JSInterface(GBDevice device, UUID mUuid) { - LOG.debug("Creating JS interface"); + LOG.debug("Creating JS interface for UUID: " + mUuid.toString()); this.device = device; this.mUuid = mUuid; } From c9cfaa9bd831efd094f2631d52ef560100519d24 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Tue, 3 Jan 2017 15:04:51 +0100 Subject: [PATCH 08/48] Pebble: webview. Implement two way communication with JS. The support within JS is a bit hacky and sometimes conflicts with the configuration page. --- .../app_config/js/gadgetbridge_boilerplate.js | 8 ++- .../devices/pebble/PebbleIoThread.java | 6 ++ .../gadgetbridge/util/WebViewSingleton.java | 59 ++++++++++++++++++- 3 files changed, 69 insertions(+), 4 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 3495b0c58..008ae7320 100644 --- a/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js +++ b/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js @@ -141,7 +141,11 @@ function gbPebble() { this.sendAppMessage = function (dict, callbackAck, callbackNack){ try { self.configurationValues = JSON.stringify(dict); - document.getElementById("jsondata").innerHTML=self.configurationValues; + if (document.getElementById("step2").style.display == 'block') { //intercept the values + document.getElementById("jsondata").innerHTML=self.configurationValues; + } else { //pass them silently + GBjs.sendAppMessage(JSON.stringify(dict)); + } return callbackAck; } catch (e) { @@ -179,8 +183,8 @@ function gbPebble() { if (str.split(needle)[1] !== undefined) { var t = new Object(); t.response = decodeURIComponent(str.split(needle)[1]); - self.evaluate('webviewclosed',[t]); showStep("step2"); + self.evaluate('webviewclosed',[t]); } else { console.error("No valid configuration found in the entered string."); } 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 bc3fd2026..6995799e5 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 @@ -140,6 +140,11 @@ class PebbleIoThread extends GBDeviceIoThread { } }; + private void sendAppMessageJS(GBDeviceEventAppMessage appMessage) { + WebViewSingleton.getorInitWebView(getContext(), gbDevice, appMessage.appUUID); + WebViewSingleton.appMessage(appMessage.message); + } + private void sendAppMessageIntent(GBDeviceEventAppMessage appMessage) { Intent intent = new Intent(); intent.setAction(PEBBLEKIT_ACTION_APP_RECEIVE); @@ -582,6 +587,7 @@ class PebbleIoThread extends GBDeviceIoThread { setInstallSlot(appInfoEvent.freeSlot); return false; } else if (deviceEvent instanceof GBDeviceEventAppMessage) { + sendAppMessageJS((GBDeviceEventAppMessage) deviceEvent); if (mEnablePebblekit) { LOG.info("Got AppMessage event"); sendAppMessageIntent((GBDeviceEventAppMessage) deviceEvent); 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 f1a8a58cb..8a2786f3e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -19,6 +19,7 @@ import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Toast; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.slf4j.Logger; @@ -32,6 +33,7 @@ import java.io.UnsupportedEncodingException; import java.io.Writer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.HashMap; import java.util.Iterator; import java.util.Scanner; import java.util.UUID; @@ -107,18 +109,22 @@ public class WebViewSingleton extends Activity { } public static void appMessage(final String message) { + + final String appMessage = jsInterface.parseIncomingAppMessage(message); + LOG.debug("to WEBVIEW: " + appMessage); + new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - instance.evaluateJavascript("Pebble.evaluate('appmessage',[{'payload':" + message + "}]);", new ValueCallback() { + instance.evaluateJavascript("Pebble.evaluate('appmessage',[" + appMessage + "]);", new ValueCallback() { @Override public void onReceiveValue(String s) { LOG.debug("Callback from showConfiguration", s); } }); } else { - instance.loadUrl("javascript:Pebble.evaluate('appmessage',[{'payload':" + message + "}]);"); + instance.loadUrl("javascript:Pebble.evaluate('appmessage',[" + appMessage + "]);"); } } }); @@ -129,6 +135,13 @@ public class WebViewSingleton extends Activity { @Override public void run() { if (instance != null) { + instance.setWebChromeClient(null); + instance.setWebViewClient(null); + instance.clearHistory(); + instance.clearCache(true); + instance.loadUrl("about:blank"); + instance.freeMemory(); + instance.pauseTimers(); instance.destroy(); instance = null; contextWrapper = null; @@ -202,6 +215,48 @@ public class WebViewSingleton extends Activity { 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(); + } + @JavascriptInterface public void gbLog(String msg) { LOG.debug("WEBVIEW", msg); From d6dd03a06514c59357046db8bc3133b072e033fe Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Sat, 28 Jan 2017 17:45:23 +0100 Subject: [PATCH 09/48] Pebble: webview add safety check --- .../freeyourgadget/gadgetbridge/util/WebViewSingleton.java | 2 ++ 1 file changed, 2 insertions(+) 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 8a2786f3e..d7b8e7750 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -110,6 +110,8 @@ public class WebViewSingleton extends Activity { public static void appMessage(final String message) { + if (instance == null) + return; final String appMessage = jsInterface.parseIncomingAppMessage(message); LOG.debug("to WEBVIEW: " + appMessage); From a4ac108287113ea1bee5a437de65eab13c8c8772 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Sat, 25 Feb 2017 15:44:37 +0100 Subject: [PATCH 10/48] Pebble: merge master moving the location override to WebViewSingleton --- .../activities/ExternalPebbleJSActivity.java | 2 - .../gadgetbridge/util/WebViewSingleton.java | 95 +++++++++++++++---- 2 files changed, 77 insertions(+), 20 deletions(-) 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 a7e0546dd..b103d1f95 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java @@ -24,8 +24,6 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton; -import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils; -import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class ExternalPebbleJSActivity extends GBActivity { 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 d7b8e7750..89c5f9aa8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -1,13 +1,19 @@ package nodomain.freeyourgadget.gadgetbridge.util; +import android.Manifest; 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.v4.app.ActivityCompat; import android.webkit.ConsoleMessage; import android.webkit.JavascriptInterface; import android.webkit.ValueCallback; @@ -52,7 +58,24 @@ public class WebViewSingleton extends Activity { private WebViewSingleton() { } - public static WebView getorInitWebView(Context context, GBDevice device, UUID uuid) { + public static WebView getorInitWebView(Context context, final GBDevice device, final UUID uuid) { + + if (jsInterface == null || (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() { + if (instance != null) { + 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()); + } + if (context instanceof Activity) { if (contextWrapper != null) { contextWrapper.setBaseContext(context); @@ -61,10 +84,12 @@ public class WebViewSingleton extends Activity { } if (instance == null) { - instance = new WebView(contextWrapper); + LOG.debug("WEBV: creating webview"); + new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { + instance = new WebView(contextWrapper); instance.setWillNotDraw(true); instance.clearCache(true); instance.setWebViewClient(new GBWebClient()); @@ -89,22 +114,6 @@ public class WebViewSingleton extends Activity { LOG.debug("WEBV: not using the passed context, as it is not an activity"); } - if (jsInterface == null || (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() { - if (instance != null) { - 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()); - } - return instance; } @@ -259,6 +268,11 @@ public class WebViewSingleton extends Activity { return jsAppMessage.toString(); } + + private boolean isLocationEnabledForWatchApp() { + return true; //as long as we don't give watchapp internet access it's not a problem + } + @JavascriptInterface public void gbLog(String msg) { LOG.debug("WEBVIEW", msg); @@ -402,6 +416,51 @@ public class WebViewSingleton extends Activity { return "gb" + this.mUuid.toString(); } + + @JavascriptInterface + public String getCurrentPosition() { + if (!isLocationEnabledForWatchApp()) { + return ""; + } + //we need to override this because the coarse location is not enough for the android webview, we should add the permission for fine location. + JSONObject geoPosition = new JSONObject(); + JSONObject coords = new JSONObject(); + try { + + Prefs prefs = GBApplication.getPrefs(); + + geoPosition.put("timestamp", (System.currentTimeMillis() / 1000) - 86400); //let JS know this value is really old + + coords.put("latitude", prefs.getFloat("location_latitude", 0)); + coords.put("longitude", prefs.getFloat("location_longitude", 0)); + + if (ActivityCompat.checkSelfPermission(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); + Criteria criteria = new Criteria(); + String provider = locationManager.getBestProvider(criteria, false); + if (provider != null) { + Location lastKnownLocation = locationManager.getLastKnownLocation(provider); + if (lastKnownLocation != null) { + geoPosition.put("timestamp", lastKnownLocation.getTime()); + + coords.put("latitude", (float) lastKnownLocation.getLatitude()); + coords.put("longitude", (float) lastKnownLocation.getLongitude()); + coords.put("accuracy", lastKnownLocation.getAccuracy()); + coords.put("altitude", lastKnownLocation.getAltitude()); + coords.put("speed", lastKnownLocation.getSpeed()); + } + } + } + + geoPosition.put("coords", coords); + + } catch (JSONException e) { + e.printStackTrace(); + } + return geoPosition.toString(); + } + } } From 711800f3d0a3028c65108218de190776950a295c Mon Sep 17 00:00:00 2001 From: cpfeiffer Date: Sat, 25 Feb 2017 18:01:08 +0100 Subject: [PATCH 11/48] Collaborative attempt to initialize the background webview on startup --- app/src/main/AndroidManifest.xml | 4 + .../gadgetbridge/GBApplication.java | 7 ++ .../activities/BackgroundWebViewActivity.java | 16 ++++ .../activities/ExternalPebbleJSActivity.java | 4 +- .../AbstractAppManagerFragment.java | 2 - .../devices/pebble/PebbleIoThread.java | 5 +- .../gadgetbridge/util/WebViewSingleton.java | 82 ++++++++----------- app/src/main/res/values/strings.xml | 1 + 8 files changed, 67 insertions(+), 54 deletions(-) create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b4132de8f..97c8d09d5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -281,6 +281,10 @@ android:name=".activities.DiscoveryActivity" android:label="@string/title_activity_discovery" android:parentActivityName=".activities.ControlCenter" /> + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java index 1973e4e41..599b53c13 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java @@ -27,6 +27,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import nodomain.freeyourgadget.gadgetbridge.activities.BackgroundWebViewActivity; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.database.DBOpenHelper; @@ -124,6 +125,8 @@ public class GBApplication extends Application { deviceManager = new DeviceManager(this); + createWebViewActivity(); + deviceService = createDeviceService(); loadBlackList(); @@ -132,6 +135,10 @@ public class GBApplication extends Application { } } + private void createWebViewActivity() { + startActivity(new Intent(getContext(), BackgroundWebViewActivity.class)); + } + @Override public void onTrimMemory(int level) { super.onTrimMemory(level); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java new file mode 100644 index 000000000..9b3fda2d3 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java @@ -0,0 +1,16 @@ +package nodomain.freeyourgadget.gadgetbridge.activities; + +import android.app.Activity; +import android.os.Bundle; +import android.os.PersistableBundle; + +import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton; + +public class BackgroundWebViewActivity extends Activity { + @Override + public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) { + super.onCreate(savedInstanceState, persistentState); + WebViewSingleton.createWebView(this); + setVisible(false); + } +} 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 b103d1f95..a7985dbb9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java @@ -48,8 +48,8 @@ public class ExternalPebbleJSActivity extends GBActivity { setContentView(R.layout.activity_external_pebble_js); - myWebView = WebViewSingleton.getorInitWebView(this, mGBDevice, appUuid); - myWebView.setWillNotDraw(false); + WebViewSingleton.updateActivityContext(this); + myWebView = WebViewSingleton.getWebView(); myWebView.addJavascriptInterface(new ActivityJSInterface(ExternalPebbleJSActivity.this), "GBActivity"); FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder); fl.addView(myWebView); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java index 090560e7a..023a36848 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java @@ -338,8 +338,6 @@ public abstract class AbstractAppManagerFragment extends Fragment { view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); popupMenu.show(); - //TODO: replace with local broadcast on app start - WebViewSingleton.getorInitWebView(getActivity(), mGBDevice, selectedApp.getUUID()); return true; } 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 2a116a451..6f3348d64 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,7 +82,7 @@ class PebbleIoThread extends GBDeviceIoThread { private int mBytesWritten = -1; private void sendAppMessageJS(GBDeviceEventAppMessage appMessage) { - WebViewSingleton.getorInitWebView(getContext(), gbDevice, appMessage.appUUID); +// WebViewSingleton.runJavascriptInterface(gbDevice, appMessage.appUUID); WebViewSingleton.appMessage(appMessage.message); } @@ -487,8 +487,7 @@ class PebbleIoThread extends GBDeviceIoThread { break; case START: LOG.info("got GBDeviceEventAppManagement START event for uuid: " + appMgmt.uuid); - WebViewSingleton.getorInitWebView(getContext(), gbDevice, appMgmt.uuid); - //TODO: the method call above will not work the first time as we need an activity. Either we find a way to have one here, or replace it with a local broadcast + WebViewSingleton.runJavascriptInterface(gbDevice, appMgmt.uuid); break; default: break; 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 89c5f9aa8..ce453821b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -13,6 +13,7 @@ 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; import android.webkit.JavascriptInterface; @@ -58,63 +59,50 @@ public class WebViewSingleton extends Activity { private WebViewSingleton() { } - public static WebView getorInitWebView(Context context, final GBDevice device, final UUID uuid) { + public static synchronized WebView createWebView(Context context) { + if (instance == null) { + contextWrapper = new MutableContextWrapper(context); + instance = new WebView(contextWrapper); + instance.setWillNotDraw(true); + instance.clearCache(true); + instance.setWebViewClient(new GBWebClient()); + instance.setWebChromeClient(new GBChromeClient()); + instance.setWebContentsDebuggingEnabled(true); + WebSettings webSettings = instance.getSettings(); + webSettings.setJavaScriptEnabled(true); + //needed to access the DOM + webSettings.setDomStorageEnabled(true); + //needed for localstorage + webSettings.setDatabaseEnabled(true); + } + return instance; + } - if (jsInterface == null || (jsInterface != null && (!device.equals(jsInterface.device) || !uuid.equals(jsInterface.mUuid)))) { + public static void updateActivityContext(Context context) { + if (context instanceof Activity) { + contextWrapper.setBaseContext(context); + } + } + + @NonNull + public static WebView getWebView() { + 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() { - if (instance != null) { - instance.removeJavascriptInterface("GBjs"); - instance.addJavascriptInterface(jsInterface, "GBjs"); - instance.loadUrl("file:///android_asset/app_config/configure.html"); - } + 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()); } - - if (context instanceof Activity) { - if (contextWrapper != null) { - contextWrapper.setBaseContext(context); - } else { - contextWrapper = new MutableContextWrapper(context); - } - - if (instance == null) { - LOG.debug("WEBV: creating webview"); - - new Handler(Looper.getMainLooper()).post(new Runnable() { - @Override - public void run() { - instance = new WebView(contextWrapper); - instance.setWillNotDraw(true); - instance.clearCache(true); - instance.setWebViewClient(new GBWebClient()); - instance.setWebChromeClient(new GBChromeClient()); - instance.setWebContentsDebuggingEnabled(true); - WebSettings webSettings = instance.getSettings(); - webSettings.setJavaScriptEnabled(true); - //needed to access the DOM - webSettings.setDomStorageEnabled(true); - //needed for localstorage - webSettings.setDatabaseEnabled(true); - if (jsInterface != null) { - LOG.debug("Attaching the existing jsInterface to the new webview instance"); - instance.addJavascriptInterface(jsInterface, "GBjs"); - instance.loadUrl("file:///android_asset/app_config/configure.html"); - } - } - }); - } - - } else { - LOG.debug("WEBV: not using the passed context, as it is not an activity"); - } - - return instance; } public static void appMessage(final String message) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7e3d0d247..fbbe82e27 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -393,4 +393,5 @@ 24H AM/PM Alarm Clock + Web View Activity From 234beace29f193a9463ef88fe75282cb1cf64053 Mon Sep 17 00:00:00 2001 From: cpfeiffer Date: Sat, 25 Feb 2017 21:50:05 +0100 Subject: [PATCH 12/48] Some fixes for the background webview --- app/src/main/AndroidManifest.xml | 3 +-- .../freeyourgadget/gadgetbridge/GBApplication.java | 4 +++- .../activities/BackgroundWebViewActivity.java | 12 ++++++++---- .../activities/ExternalPebbleJSActivity.java | 3 ++- .../gadgetbridge/util/WebViewSingleton.java | 12 ++++++++++-- 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 97c8d09d5..d6235c448 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -283,8 +283,7 @@ android:parentActivityName=".activities.ControlCenter" /> + android:label="@string/activity_web_view"/> diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java index 599b53c13..54fe09288 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java @@ -136,7 +136,9 @@ public class GBApplication extends Application { } private void createWebViewActivity() { - startActivity(new Intent(getContext(), BackgroundWebViewActivity.class)); + Intent intent = new Intent(getContext(), BackgroundWebViewActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java index 9b3fda2d3..7acd28ccc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java @@ -2,15 +2,19 @@ package nodomain.freeyourgadget.gadgetbridge.activities; import android.app.Activity; import android.os.Bundle; -import android.os.PersistableBundle; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton; public class BackgroundWebViewActivity extends Activity { + private static Logger LOG = LoggerFactory.getLogger(BackgroundWebViewActivity.class); + @Override - public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) { - super.onCreate(savedInstanceState, persistentState); + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); WebViewSingleton.createWebView(this); - setVisible(false); + finish(); } } 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 a7985dbb9..5f98350a2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java @@ -50,6 +50,8 @@ public class ExternalPebbleJSActivity extends GBActivity { WebViewSingleton.updateActivityContext(this); myWebView = WebViewSingleton.getWebView(); + myWebView.setWillNotDraw(false); + myWebView.removeJavascriptInterface("GBActivity"); myWebView.addJavascriptInterface(new ActivityJSInterface(ExternalPebbleJSActivity.this), "GBActivity"); FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder); fl.addView(myWebView); @@ -104,7 +106,6 @@ public class ExternalPebbleJSActivity extends GBActivity { fl.removeAllViews(); } }); - } @Override 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 ce453821b..b3cf91a84 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -48,7 +48,7 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -public class WebViewSingleton extends Activity { +public class WebViewSingleton { private static final Logger LOG = LoggerFactory.getLogger(WebViewSingleton.class); @@ -154,7 +154,7 @@ public class WebViewSingleton extends Activity { @Override public boolean onConsoleMessage(ConsoleMessage consoleMessage) { if (ConsoleMessage.MessageLevel.ERROR.equals(consoleMessage.messageLevel())) { - GB.toast(consoleMessage.message(), Toast.LENGTH_LONG, GB.ERROR); + GB.toast(formatConsoleMessage(consoleMessage), Toast.LENGTH_LONG, GB.ERROR); //TODO: show error page } return super.onConsoleMessage(consoleMessage); @@ -162,6 +162,14 @@ public class WebViewSingleton extends Activity { } + private static String formatConsoleMessage(ConsoleMessage message) { + String sourceId = message.sourceId(); + if (sourceId == null || sourceId.length() == 0) { + sourceId = "unknown"; + } + return String.format("%s (at %s: %d)", message.message(), sourceId, message.lineNumber()); + } + private static class GBWebClient extends WebViewClient { @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { From 0038ddabdbe6380e4dd62ce7cb2b3dfe188d093f Mon Sep 17 00:00:00 2001 From: cpfeiffer Date: Sat, 25 Feb 2017 21:52:05 +0100 Subject: [PATCH 13/48] Make the initial webview activity translucent --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d6235c448..91801ae5e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -283,6 +283,7 @@ android:parentActivityName=".activities.ControlCenter" /> Date: Sat, 25 Feb 2017 22:02:40 +0100 Subject: [PATCH 14/48] Only start the webview when needed --- .../gadgetbridge/GBApplication.java | 9 ++++++--- .../activities/BackgroundWebViewActivity.java | 5 ----- .../devices/AbstractDeviceCoordinator.java | 5 +++++ .../gadgetbridge/devices/DeviceCoordinator.java | 8 ++++++++ .../devices/pebble/PebbleCoordinator.java | 5 +++++ .../gadgetbridge/util/DeviceHelper.java | 16 ++++++++++++++++ 6 files changed, 40 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java index 54fe09288..9195981b5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java @@ -37,6 +37,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; +import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs; @@ -136,9 +137,11 @@ public class GBApplication extends Application { } private void createWebViewActivity() { - Intent intent = new Intent(getContext(), BackgroundWebViewActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); + if (DeviceHelper.getInstance().needsBackgroundWebView(this)) { + Intent intent = new Intent(getContext(), BackgroundWebViewActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java index 7acd28ccc..efb88399c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java @@ -3,14 +3,9 @@ package nodomain.freeyourgadget.gadgetbridge.activities; import android.app.Activity; import android.os.Bundle; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton; public class BackgroundWebViewActivity extends Activity { - private static Logger LOG = LoggerFactory.getLogger(BackgroundWebViewActivity.class); - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java index 46a65e09c..82ae6f59b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java @@ -103,4 +103,9 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator { } return false; } + + @Override + public boolean needsBackgroundWebView(GBDevice device) { + return false; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java index 346446da0..aa606c589 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java @@ -186,4 +186,12 @@ public interface DeviceCoordinator { * @return */ Class getAppsManagementActivity(); + + /** + * Returns true if the given device needs a background webview for + * executing javascript or configuration, for example. + * + * @param device + */ + boolean needsBackgroundWebView(GBDevice device); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java index 316967cb0..81d694b9d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java @@ -137,4 +137,9 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator { public Class getAppsManagementActivity() { return AppManagerActivity.class; } + + @Override + public boolean needsBackgroundWebView(GBDevice device) { + return true; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java index 82791f62c..514e599e9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java @@ -255,4 +255,20 @@ public class DeviceHelper { } return false; } + + /** + * Returns true if the background webview for executing javascript is needed + * for any of the known/available devices. + * @param context + */ + public boolean needsBackgroundWebView(Context context) { + Set availableDevices = getAvailableDevices(context); + for (GBDevice device : availableDevices) { + DeviceCoordinator coordinator = getCoordinator(device); + if (coordinator.needsBackgroundWebView(device)) { + return true; + } + } + return false; + } } From a545c566807ef9e880aac2e9024efea25cf6da1b Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Sun, 26 Feb 2017 17:57:26 +0100 Subject: [PATCH 15/48] Pebble: ack the messages that go to the webview and do not destroy it on disconnect. --- .../devices/pebble/PebbleIoThread.java | 1 + .../gadgetbridge/util/WebViewSingleton.java | 19 ++++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) 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 6f3348d64..569b17d08 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 @@ -84,6 +84,7 @@ class PebbleIoThread extends GBDeviceIoThread { private void sendAppMessageJS(GBDeviceEventAppMessage appMessage) { // WebViewSingleton.runJavascriptInterface(gbDevice, appMessage.appUUID); WebViewSingleton.appMessage(appMessage.message); + write(mPebbleProtocol.encodeApplicationMessageAck(appMessage.appUUID, (byte) appMessage.id)); } PebbleIoThread(PebbleSupport pebbleSupport, GBDevice gbDevice, GBDeviceProtocol gbDeviceProtocol, BluetoothAdapter btAdapter, Context context) { 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 b3cf91a84..7cc7a636a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -105,10 +105,11 @@ public class WebViewSingleton { } } - public static void appMessage(final String message) { + public static void appMessage(String message) { if (instance == null) return; + final String appMessage = jsInterface.parseIncomingAppMessage(message); LOG.debug("to WEBVIEW: " + appMessage); @@ -119,7 +120,8 @@ public class WebViewSingleton { instance.evaluateJavascript("Pebble.evaluate('appmessage',[" + appMessage + "]);", new ValueCallback() { @Override public void onReceiveValue(String s) { - LOG.debug("Callback from showConfiguration", s); + //TODO: the message should be acked here instead of in PebbleIoThread + LOG.debug("Callback from appmessage", s); } }); } else { @@ -141,9 +143,9 @@ public class WebViewSingleton { instance.loadUrl("about:blank"); instance.freeMemory(); instance.pauseTimers(); - instance.destroy(); - instance = null; - contextWrapper = null; +// instance.destroy(); +// instance = null; +// contextWrapper = null; jsInterface = null; } } @@ -258,6 +260,13 @@ public class WebViewSingleton { } jsAppMessage.put("payload", outgoing); + //ack message to pebble + + /* + sendBytesAck = new GBDeviceEventSendBytes(); + sendBytesAck.encodedBytes = encodeApplicationMessageAck(uuid, last_id); + */ + } catch (JSONException e) { e.printStackTrace(); } From 21498bd2e96e00160395d33d689fc160b47bc933 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Mon, 27 Feb 2017 19:02:34 +0100 Subject: [PATCH 16/48] Pebble: mimic openweatherapi response from our cached data. This should allow watchfaces using openweathermap to run without modification (but we lack the icon ATM). Tested with multifit watchface. Also centralize the location data in own local class and remove leftover comment. --- .../gadgetbridge/util/WebViewSingleton.java | 161 ++++++++++++++---- 1 file changed, 128 insertions(+), 33 deletions(-) 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 7cc7a636a..49e941383 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -1,6 +1,7 @@ package nodomain.freeyourgadget.gadgetbridge.util; import android.Manifest; +import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -26,6 +27,9 @@ import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.Toast; +import net.e175.klaus.solarpositioning.DeltaT; +import net.e175.klaus.solarpositioning.SPA; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -33,6 +37,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedWriter; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileWriter; import java.io.IOException; @@ -40,6 +45,7 @@ import java.io.UnsupportedEncodingException; import java.io.Writer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.Scanner; @@ -47,6 +53,8 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.Weather; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; public class WebViewSingleton { @@ -152,6 +160,101 @@ public class WebViewSingleton { }); } + private static class CurrentPosition { + long timestamp; + double altitude; + float latitude, longitude, accuracy, speed; + + public CurrentPosition() { + Prefs prefs = GBApplication.getPrefs(); + this.latitude = prefs.getFloat("location_latitude", 0); + this.longitude = prefs.getFloat("location_longitude", 0); + LOG.info("got longitude/latitude from preferences: " + latitude + "/" + longitude); + + 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 && + prefs.getBoolean("use_updated_location_if_available", false)) { + LocationManager locationManager = (LocationManager) contextWrapper.getSystemService(Context.LOCATION_SERVICE); + Criteria criteria = new Criteria(); + String provider = locationManager.getBestProvider(criteria, false); + if (provider != null) { + Location lastKnownLocation = locationManager.getLastKnownLocation(provider); + if (lastKnownLocation != null) { + this.timestamp = lastKnownLocation.getTime(); + + this.latitude = (float) lastKnownLocation.getLatitude(); + this.longitude = (float) lastKnownLocation.getLongitude(); + this.accuracy = (float) lastKnownLocation.getAccuracy(); + this.altitude = (float) lastKnownLocation.getAltitude(); + this.speed = (float) lastKnownLocation.getSpeed(); + + } + } + } + + + } + } + + + private static WebResourceResponse mimicOpenWeatherMapResponse() { + WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec(); + + if (weatherSpec == null) return null; + //location block + + CurrentPosition currentPosition = new CurrentPosition(); + GregorianCalendar[] sunrise = SPA.calculateSunriseTransitSet(new GregorianCalendar(), currentPosition.latitude, currentPosition.longitude, DeltaT.estimate(new GregorianCalendar())); + + JSONObject resp = new JSONObject(); + JSONObject coord = new JSONObject(); + JSONObject sys = new JSONObject(); + JSONArray weather = new JSONArray(); + JSONObject currCond = new JSONObject(); + JSONObject main = new JSONObject(); + try { + coord.put("lat", currentPosition.latitude); + coord.put("lon", currentPosition.longitude); + + sys.put("country", "World"); + sys.put("sunrise", (sunrise[0].getTimeInMillis() / 1000)); + sys.put("sunset", (sunrise[2].getTimeInMillis() / 1000)); + + currCond.put("id", weatherSpec.currentConditionCode); + currCond.put("main", weatherSpec.currentCondition); + weather.put(currCond); + + main.put("temp", weatherSpec.currentTemp); + main.put("temp_min", weatherSpec.todayMinTemp); + main.put("temp_max", weatherSpec.todayMaxTemp); + main.put("name", weatherSpec.location); + + resp.put("coord", coord); + resp.put("sys", sys); + resp.put("weather", weather); + resp.put("main", main); + + LOG.info("WEBVIEW - mimic openweather response" + resp.toString()); + HashMap headers = new HashMap<>(); + headers.put("Access-Control-Allow-Origin", "*"); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return new WebResourceResponse("application/json", "utf-8", 200, "OK", + headers, + new ByteArrayInputStream(resp.toString().getBytes()) + ); + } else { + return new WebResourceResponse("application/json", "utf-8", new ByteArrayInputStream(resp.toString().getBytes())); + } + } catch (JSONException e) { + e.printStackTrace(); + } + + return null; + + } + private static class GBChromeClient extends WebChromeClient { @Override public boolean onConsoleMessage(ConsoleMessage consoleMessage) { @@ -173,15 +276,29 @@ public class WebViewSingleton { } private static class GBWebClient extends WebViewClient { + @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override 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")) { + return mimicOpenWeatherMapResponse(); + } else { + LOG.debug("WEBVIEW request:" + request.getUrl().toString() + " denied"); } - LOG.debug("WEBVIEW request:" + request.toString()); return super.shouldInterceptRequest(view, request); } + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, String url) { + LOG.debug("WEBVIEW shouldInterceptRequest URL" + url); + if (url.startsWith("http://api.openweathermap.org") || url.startsWith("https://api.openweathermap.org")) { + return mimicOpenWeatherMapResponse(); + } else { + LOG.debug("WEBVIEW request:" + url + " denied"); + } + return super.shouldInterceptRequest(view, url); + } + @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { if (url.startsWith("http://") || url.startsWith("https://")) { @@ -260,13 +377,6 @@ public class WebViewSingleton { } jsAppMessage.put("payload", outgoing); - //ack message to pebble - - /* - sendBytesAck = new GBDeviceEventSendBytes(); - sendBytesAck.encodedBytes = encodeApplicationMessageAck(uuid, last_id); - */ - } catch (JSONException e) { e.printStackTrace(); } @@ -432,37 +542,22 @@ public class WebViewSingleton { JSONObject coords = new JSONObject(); try { - Prefs prefs = GBApplication.getPrefs(); + CurrentPosition currentPosition = new CurrentPosition(); - geoPosition.put("timestamp", (System.currentTimeMillis() / 1000) - 86400); //let JS know this value is really old + geoPosition.put("timestamp", currentPosition.timestamp); - coords.put("latitude", prefs.getFloat("location_latitude", 0)); - coords.put("longitude", prefs.getFloat("location_longitude", 0)); - - if (ActivityCompat.checkSelfPermission(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); - Criteria criteria = new Criteria(); - String provider = locationManager.getBestProvider(criteria, false); - if (provider != null) { - Location lastKnownLocation = locationManager.getLastKnownLocation(provider); - if (lastKnownLocation != null) { - geoPosition.put("timestamp", lastKnownLocation.getTime()); - - coords.put("latitude", (float) lastKnownLocation.getLatitude()); - coords.put("longitude", (float) lastKnownLocation.getLongitude()); - coords.put("accuracy", lastKnownLocation.getAccuracy()); - coords.put("altitude", lastKnownLocation.getAltitude()); - coords.put("speed", lastKnownLocation.getSpeed()); - } - } - } + coords.put("latitude", currentPosition.latitude); + coords.put("longitude", currentPosition.longitude); + coords.put("accuracy", currentPosition.accuracy); + coords.put("altitude", currentPosition.altitude); + coords.put("speed", currentPosition.speed); geoPosition.put("coords", coords); } catch (JSONException e) { e.printStackTrace(); } + LOG.info("WEBVIEW - geo position" + geoPosition.toString()); return geoPosition.toString(); } From 593b169f003100a426d4dec41650ddd0bce036e7 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Mon, 27 Feb 2017 22:28:42 +0100 Subject: [PATCH 17/48] Pebble: Put icon into fake weather response, disable mario time native handler (since it works a bit now) :) --- .../gadgetbridge/service/devices/pebble/PebbleProtocol.java | 2 +- .../freeyourgadget/gadgetbridge/util/WebViewSingleton.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 8e6ed14da..d637fe2c0 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 @@ -387,7 +387,7 @@ public class PebbleProtocol extends GBDeviceProtocol { 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_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_TREKVOLLE, new AppMessageHandlerTrekVolle(UUID_TREKVOLLE, PebbleProtocol.this)); mAppMessageHandlers.put(UUID_SQUARE, new AppMessageHandlerSquare(UUID_SQUARE, PebbleProtocol.this)); 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 49e941383..8e925f7d0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -223,6 +223,7 @@ public class WebViewSingleton { currCond.put("id", weatherSpec.currentConditionCode); currCond.put("main", weatherSpec.currentCondition); + currCond.put("icon", Weather.mapToOpenWeatherMapIcon(weatherSpec.currentConditionCode)); weather.put(currCond); main.put("temp", weatherSpec.currentTemp); @@ -234,7 +235,6 @@ public class WebViewSingleton { resp.put("sys", sys); resp.put("weather", weather); resp.put("main", main); - LOG.info("WEBVIEW - mimic openweather response" + resp.toString()); HashMap headers = new HashMap<>(); headers.put("Access-Control-Allow-Origin", "*"); From 76be0ae676e86ffccae74408b7e681d63747e0c4 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Tue, 28 Feb 2017 21:11:26 +0100 Subject: [PATCH 18/48] 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"); From f84c651c384bb34f7be2a12adf24b7cce80ed624 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Sat, 4 Mar 2017 19:46:18 +0100 Subject: [PATCH 19/48] Pebble background webview: config page should be shown reliably now. --- .../app_config/js/gadgetbridge_boilerplate.js | 9 ++++++ .../activities/ExternalPebbleJSActivity.java | 21 +++++-------- .../gadgetbridge/util/WebViewSingleton.java | 30 +++++++++++++------ 3 files changed, 38 insertions(+), 22 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 96dde3051..081118ca8 100644 --- a/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js +++ b/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js @@ -99,6 +99,7 @@ function gbPebble() { for (var i = 0; i < l; i++) { evs[i].apply(null, args); } + GBjs.eventFinished(name); } this.actuallyOpenURL = function() { @@ -204,6 +205,14 @@ document.addEventListener('DOMContentLoaded', function(){ if (jsConfigFile != null) { loadScript(jsConfigFile, function() { Pebble.evaluate('ready'); + if(document.hasFocus() && !(getURLVariable('config') == 'true')) { + Pebble.evaluate('showConfiguration'); + } else { + window.onfocus = function () { + GBjs.gbLog("window focused!!!"); + Pebble.evaluate('showConfiguration'); + }; + } if (getURLVariable('config') == 'true') { showStep("step2"); var json_string = getURLVariable('json'); 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 95802efdd..b3db976c9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java @@ -8,7 +8,6 @@ import android.support.v4.app.NavUtils; import android.view.MenuItem; import android.view.View; import android.webkit.JavascriptInterface; -import android.webkit.ValueCallback; import android.webkit.WebView; import android.widget.FrameLayout; import android.widget.Toast; @@ -28,9 +27,7 @@ public class ExternalPebbleJSActivity extends GBActivity { private static final Logger LOG = LoggerFactory.getLogger(ExternalPebbleJSActivity.class); - private UUID appUuid; private Uri confUri; - private GBDevice mGBDevice = null; private WebView myWebView; @Override @@ -39,8 +36,7 @@ public class ExternalPebbleJSActivity extends GBActivity { Bundle extras = getIntent().getExtras(); if (extras != null) { - mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE); - appUuid = (UUID) extras.getSerializable(DeviceService.EXTRA_APP_UUID); + WebViewSingleton.runJavascriptInterface((GBDevice) extras.getParcelable(GBDevice.EXTRA_DEVICE), (UUID) extras.getSerializable(DeviceService.EXTRA_APP_UUID)); } else { throw new IllegalArgumentException("Must provide a device when invoking this activity"); } @@ -61,13 +57,13 @@ public class ExternalPebbleJSActivity extends GBActivity { public void onViewAttachedToWindow(View v) { v.setLayerType(View.LAYER_TYPE_HARDWARE, null); - //show configuration - myWebView.evaluateJavascript("Pebble.evaluate('showConfiguration');", new ValueCallback() { - @Override - public void onReceiveValue(String s) { - LOG.debug("Callback from showConfiguration: " + s); - } - }); + //show configuration - moved to JS +// myWebView.evaluateJavascript("Pebble.evaluate('showConfiguration');", new ValueCallback() { +// @Override +// public void onReceiveValue(String s) { +// LOG.debug("Callback from showConfiguration: " + s); +// } +// }); } @Override @@ -88,7 +84,6 @@ public class ExternalPebbleJSActivity extends GBActivity { //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); 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 a96575165..fd53c95c6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -60,6 +60,7 @@ public class WebViewSingleton { private static WebView instance = null; private Activity contextWrapper; private static WebViewSingleton webViewSingleton = new WebViewSingleton(); + private static UUID currentRunningUUID; private WebViewSingleton() { } @@ -98,14 +99,21 @@ public class WebViewSingleton { final JSInterface jsInterface = new JSInterface(device, uuid); - webViewSingleton.contextWrapper.runOnUiThread(new Runnable() { - @Override - public void run() { - instance.removeJavascriptInterface("GBjs"); - instance.addJavascriptInterface(jsInterface, "GBjs"); - instance.loadUrl("file:///android_asset/app_config/configure.html?rand=" + Math.random() * 500); - } - }); + if (uuid.equals(currentRunningUUID)) { + LOG.debug("WEBVIEW uuid not changed keeping the old context"); + } else { + LOG.debug("WEBVIEW uuid changed, restarting"); + currentRunningUUID = uuid; + webViewSingleton.contextWrapper.runOnUiThread(new Runnable() { + @Override + public void run() { + 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) { @@ -390,7 +398,7 @@ public class WebViewSingleton { @JavascriptInterface public void gbLog(String msg) { - LOG.debug("WEBVIEW webpage log", msg); + LOG.debug("WEBVIEW webpage log: " + msg); } @JavascriptInterface @@ -562,6 +570,10 @@ public class WebViewSingleton { return geoPosition.toString(); } + @JavascriptInterface + public void eventFinished(String event) { + LOG.debug("WEBVIEW event finished: " + event); + } } } From 75d4abc9dc1eee915fd768c92f535a92b14153c5 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Sat, 4 Mar 2017 20:43:32 +0100 Subject: [PATCH 20/48] Pebble background webview: bring back the mutableContextWrapper, otherwise inputs aren't working. Reliably go back to first step of the configuration page when closing (this causes a Toast when closing the activity while in the clay settings page) --- .../app_config/js/gadgetbridge_boilerplate.js | 2 ++ .../activities/ExternalPebbleJSActivity.java | 7 +++++++ .../gadgetbridge/util/WebViewSingleton.java | 20 +++++++++++-------- 3 files changed, 21 insertions(+), 8 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 081118ca8..d3197f108 100644 --- a/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js +++ b/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js @@ -109,6 +109,7 @@ function gbPebble() { this.actuallySendData = function() { GBjs.sendAppMessage(self.configurationValues); + showStep("step1"); GBActivity.closeActivity(); } @@ -209,6 +210,7 @@ if (jsConfigFile != null) { Pebble.evaluate('showConfiguration'); } else { window.onfocus = function () { + showStep("step1"); GBjs.gbLog("window focused!!!"); Pebble.evaluate('showConfiguration'); }; 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 b3db976c9..999d2620f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java @@ -8,6 +8,7 @@ import android.support.v4.app.NavUtils; import android.view.MenuItem; import android.view.View; import android.webkit.JavascriptInterface; +import android.webkit.ValueCallback; import android.webkit.WebView; import android.widget.FrameLayout; import android.widget.Toast; @@ -70,6 +71,12 @@ public class ExternalPebbleJSActivity extends GBActivity { public void onViewDetachedFromWindow(View v) { myWebView.removeJavascriptInterface("GBActivity"); myWebView.setWillNotDraw(true); + myWebView.evaluateJavascript("showStep('step1')", new ValueCallback() { + @Override + public void onReceiveValue(String s) { + LOG.debug("Callback from window detach: " + s); + } + }); FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder); fl.removeAllViews(); } 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 fd53c95c6..961e4cedc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -4,12 +4,15 @@ import android.Manifest; 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; @@ -58,7 +61,8 @@ public class WebViewSingleton { private static final Logger LOG = LoggerFactory.getLogger(WebViewSingleton.class); private static WebView instance = null; - private Activity contextWrapper; + private MutableContextWrapper contextWrapper; + private Looper mainLooper; private static WebViewSingleton webViewSingleton = new WebViewSingleton(); private static UUID currentRunningUUID; @@ -67,8 +71,9 @@ public class WebViewSingleton { public static synchronized WebView createWebView(Activity context) { if (instance == null) { - webViewSingleton.contextWrapper = context; - instance = new WebView(context); + webViewSingleton.contextWrapper = new MutableContextWrapper(context); + webViewSingleton.mainLooper = context.getMainLooper(); + instance = new WebView(webViewSingleton.contextWrapper); instance.setWillNotDraw(true); instance.clearCache(true); instance.setWebViewClient(new GBWebClient()); @@ -86,7 +91,7 @@ public class WebViewSingleton { public static void updateActivityContext(Activity context) { if (context instanceof Activity) { - webViewSingleton.contextWrapper = context; + webViewSingleton.contextWrapper.setBaseContext(context); } } @@ -104,7 +109,7 @@ public class WebViewSingleton { } else { LOG.debug("WEBVIEW uuid changed, restarting"); currentRunningUUID = uuid; - webViewSingleton.contextWrapper.runOnUiThread(new Runnable() { + new Handler(webViewSingleton.mainLooper).post(new Runnable() { @Override public void run() { instance.removeJavascriptInterface("GBjs"); @@ -125,8 +130,7 @@ public class WebViewSingleton { final String appMessage = parseIncomingAppMessage(message.message, message.appUUID); LOG.debug("to WEBVIEW: " + appMessage); - - webViewSingleton.contextWrapper.runOnUiThread(new Runnable() { + new Handler(webViewSingleton.mainLooper).post(new Runnable() { @Override public void run() { instance.evaluateJavascript("Pebble.evaluate('appmessage',[" + appMessage + "]);", new ValueCallback() { @@ -141,7 +145,7 @@ public class WebViewSingleton { } public static void disposeWebView() { - webViewSingleton.contextWrapper.runOnUiThread(new Runnable() { + new Handler(webViewSingleton.mainLooper).post(new Runnable() { @Override public void run() { if (instance != null) { From 453f94f8f6e9081a398740ac26b3815021b9e807 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Wed, 8 Mar 2017 10:46:33 +0100 Subject: [PATCH 21/48] Pebble: add cod=200 to fake owm json response (fixes TimeStyle cheking the response code) --- .../freeyourgadget/gadgetbridge/util/WebViewSingleton.java | 1 + 1 file changed, 1 insertion(+) 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 961e4cedc..2986d56d0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -243,6 +243,7 @@ public class WebViewSingleton { main.put("temp_max", weatherSpec.todayMaxTemp); main.put("name", weatherSpec.location); + resp.put("cod", 200); resp.put("coord", coord); resp.put("sys", sys); resp.put("weather", weather); From b3c0f4e9fd967549806e2d64d09dcc2c6540da5d Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Wed, 8 Mar 2017 11:00:46 +0100 Subject: [PATCH 22/48] Pebble: fix own fake response if units=metric was in original request --- .../gadgetbridge/util/WebViewSingleton.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) 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 2986d56d0..70b38f9f4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -207,7 +207,7 @@ public class WebViewSingleton { } - private static WebResourceResponse mimicOpenWeatherMapResponse() { + private static WebResourceResponse mimicOpenWeatherMapResponse(String origialRequestURL) { WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec(); if (weatherSpec == null) { @@ -238,9 +238,19 @@ public class WebViewSingleton { currCond.put("icon", Weather.mapToOpenWeatherMapIcon(weatherSpec.currentConditionCode)); weather.put(currCond); - main.put("temp", weatherSpec.currentTemp); - main.put("temp_min", weatherSpec.todayMinTemp); - main.put("temp_max", weatherSpec.todayMaxTemp); + int currentTemp = weatherSpec.currentTemp; + int todayMinTemp = weatherSpec.todayMinTemp; + int todayMaxTemp = weatherSpec.todayMaxTemp; + + if (origialRequestURL.contains("units=metric")) { // not nice but WebResourceRequest is not in KitKat + currentTemp -= 273; + todayMinTemp -= 273; + todayMaxTemp -= 273; + } + + main.put("temp", currentTemp); + main.put("temp_min", todayMinTemp); + main.put("temp_max", todayMaxTemp); main.put("name", weatherSpec.location); resp.put("cod", 200); @@ -294,7 +304,7 @@ public class WebViewSingleton { 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(); + return mimicOpenWeatherMapResponse(request.getUrl().toString()); } else { LOG.debug("WEBVIEW request:" + request.getUrl().toString() + " not intercepted"); } @@ -306,7 +316,7 @@ public class WebViewSingleton { public WebResourceResponse shouldInterceptRequest(WebView view, String url) { LOG.debug("WEBVIEW shouldInterceptRequest URL (legacy)" + url); if (url.startsWith("http://api.openweathermap.org") || url.startsWith("https://api.openweathermap.org")) { - return mimicOpenWeatherMapResponse(); + return mimicOpenWeatherMapResponse(url); } else { LOG.debug("WEBVIEW request:" + url + " not intercepted"); } From fb2e15dc4ab79af6fe5f2be79668f18030ea32ff Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Sat, 11 Mar 2017 12:04:54 +0100 Subject: [PATCH 23/48] remove (next) from 0.18.0 - prepareing for release --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5a608e7f..6594c5e0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ###Changelog -###Version 0.18.0 (next) +###Version 0.18.0 * Add Portuguese pt_PT and pt_BR translations * Add Czech translation * Add Hebrew translation and transliteration From f9c70714b83faff557ce799c3bcbf9770bf9f946 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Tue, 9 May 2017 14:42:19 +0200 Subject: [PATCH 24/48] revert all unrelated changes done in background-javascript branch --- .../devices/hplus/HPlusCoordinator.java | 16 ++--- .../BluetoothPairingRequestReceiver.java | 1 + .../service/btle/BleNamesResolver.java | 10 +-- .../devices/hplus/HPlusDataRecordDaySlot.java | 2 +- .../operations/FetchActivityOperation.java | 2 +- app/src/main/res/values-cs/strings.xml | 68 +++++++++---------- app/src/main/res/values-de/strings.xml | 8 +-- app/src/main/res/values-es/strings.xml | 30 ++++---- app/src/main/res/values-fr/strings.xml | 30 ++++---- app/src/main/res/values-it/strings.xml | 22 +++--- app/src/main/res/values-ja/strings.xml | 36 +++++----- app/src/main/res/values-pt-rBR/strings.xml | 2 +- 12 files changed, 114 insertions(+), 113 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusCoordinator.java index 2ae70e54b..e99123bcd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusCoordinator.java @@ -31,13 +31,6 @@ import android.os.Build; import android.os.ParcelUuid; import android.support.annotation.NonNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Collection; -import java.util.Collections; -import java.util.Locale; - import de.greenrobot.dao.query.QueryBuilder; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBException; @@ -56,6 +49,13 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.Collections; +import java.util.Locale; + import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getContext; public class HPlusCoordinator extends AbstractDeviceCoordinator { @@ -83,7 +83,7 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator { } @Override - public int getBondingStyle(GBDevice deviceCandidate) { + public int getBondingStyle(GBDevice deviceCandidate){ return BONDING_STYLE_NONE; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothPairingRequestReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothPairingRequestReceiver.java index a148a571c..5291745ac 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothPairingRequestReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothPairingRequestReceiver.java @@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BleNamesResolver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BleNamesResolver.java index 1d1592890..7276e22e5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BleNamesResolver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BleNamesResolver.java @@ -101,7 +101,7 @@ public class BleNamesResolver { mServices.put("00001804-0000-1000-8000-00805f9b34fb", "Tx Power"); mServices.put("0000fee0-0000-3512-2118-0009af100700", "(Propr: Xiaomi MiLi Service)"); mServices.put("00001530-0000-3512-2118-0009af100700", "(Propr: Xiaomi Weight Service)"); - mServices.put("14701820-620a-3973-7c78-9cfff0876abd", "(Propr: HPLUS Service)"); + mServices.put("14701820-620a-3973-7c78-9cfff0876abd", "(Propr: HPLUS Service)"); mCharacteristics.put("00002a43-0000-1000-8000-00805f9b34fb", "Alert AlertCategory ID"); @@ -185,10 +185,10 @@ public class BleNamesResolver { mCharacteristics.put("00002a0e-0000-1000-8000-00805f9b34fb", "Time Zone"); mCharacteristics.put("00002a07-0000-1000-8000-00805f9b34fb", "Tx Power Level"); mCharacteristics.put("00002a45-0000-1000-8000-00805f9b34fb", "Unread Alert Status"); - - mCharacteristics.put("14702856-620a-3973-7c78-9cfff0876abd", "(Propr: HPLUS Control)"); - mCharacteristics.put("14702853-620a-3973-7c78-9cfff0876abd", "(Propr: HPLUS Measurements)"); - mValueFormats.put(Integer.valueOf(52), "32bit float"); + + mCharacteristics.put("14702856-620a-3973-7c78-9cfff0876abd", "(Propr: HPLUS Control)"); + mCharacteristics.put("14702853-620a-3973-7c78-9cfff0876abd", "(Propr: HPLUS Measurements)"); + mValueFormats.put(Integer.valueOf(52), "32bit float"); mValueFormats.put(Integer.valueOf(50), "16bit float"); mValueFormats.put(Integer.valueOf(34), "16bit signed int"); mValueFormats.put(Integer.valueOf(36), "32bit signed int"); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySlot.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySlot.java index fb58bd138..94c8d6c8f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySlot.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusDataRecordDaySlot.java @@ -114,7 +114,7 @@ public class HPlusDataRecordDaySlot extends HPlusDataRecord { } - public boolean isValid() { + public boolean isValid(){ return steps != 0 || secondsInactive != 0 || heartRate != -1; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java index ebaf16d9a..160ce8cf1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/operations/FetchActivityOperation.java @@ -51,8 +51,8 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.BLETypeConversions; import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction; import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WaitAction; -import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.AbstractMiBand2Operation; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.MiBand2Support; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.AbstractMiBand2Operation; import nodomain.freeyourgadget.gadgetbridge.util.ArrayUtils; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 37f1dcf8a..7ea09ae6b 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -1,9 +1,9 @@ - Gadgetbridge - Gadgetbridge + Gadgetbridge + Gadgetbridge Nastavení - Ladění + Ladění Ukončit Načíst data Monitor spánku (alfa) @@ -13,13 +13,13 @@ Odstranit zařízení Odstranit %1$s Odstraní zařízení a vymaže všechna data! - Otevřít navigační lištu - Zavřít navigační lištu - Podržte kartu déle pro odpojení - Odpojuji - Připojuji - Sejmout snímek obrazovky - Ladění + Otevřít navigační lištu + Zavřít navigační lištu + Podržte kartu déle pro odpojení + Odpojuji + Připojuji + Sejmout snímek obrazovky + Ladění Manažer aplikací Aplikace v cache @@ -63,12 +63,12 @@ Tmavé Jazyk Skrýt notifikace Gadgetbridge - Ikona ve stavové liště a notifikace na zamčeném displeji budou zobrazeny - Ikona ve stavové liště a notifikace na zamčeném displeji nebudou zobrazeny + Ikona ve stavové liště a notifikace na zamčeném displeji budou zobrazeny + Ikona ve stavové liště a notifikace na zamčeném displeji nebudou zobrazeny Notifikace Opakování Volání - SMS + SMS Zpráva Pebble Podpora pro aplikace, které posílají notifikace do Pebble via PebbleKit. Obecná podpora notifikací @@ -125,7 +125,7 @@ Povolí funkce, které nebyl testovány. POVOLTE, JEN POKUD VÍTE, CO DĚLÁTE! Vždy preferovat BLE Použít experimentální podporu Pebble LE pro všechny Pebble místo BT classic, vyžaduje spárování \"Pebble LE\" po připojení bez LE - Pebble 2/LE GATT MTU limit + Pebble 2/LE GATT MTU limit Pokud vaše Pebble 2/Pebble LE nepracuje jak má, zkuste toto nastavení pro omezení MTU (povolený rozsah 20-512) Zapnout logování Watch App Logy od watch app budou logovány v Gadgetbridge (vyžaduje znovupřipojení) @@ -141,10 +141,10 @@ připojování připojeno neznámý stav - HW: %1$s FW: %2$s - FW: %1$s + HW: %1$s FW: %2$s + FW: %1$s (neznámé) - Test + Test Test notifikací Toto je notifikace z Gadgetbridge BT není podporován. @@ -167,7 +167,7 @@ Zastavit hledání Spustit hledání Připojit nové zařízení - %1$s (%2$s) + %1$s (%2$s) Párovat zařízení Použijte párování BT Androidu pro spárování se zařízením. Párovat Mi Band @@ -187,7 +187,7 @@ Data uživatele nejsou platná, nyní používám vzorová. Když Mi Band zavibruje a blikne, dotkněte se jej několikrát po sobě. Instalovat - Nastavte své zařízení pro vyhledání. Již připojená zařízení nebudou vyhledána. Zapněte lokalizaci (GPS) pro mobily s Androidem 6 a vyšším. Vypněte hlídání soukromí pro Gadgetbridge, protože může způsobit nestabilitu mobilu. Pokud není zařízení vyhledáno během několika minut, zkuste to znovu po restartu mobilu. + Nastavte své zařízení pro vyhledání. Již připojená zařízení nebudou vyhledána. Zapněte lokalizaci (GPS) pro mobily s Androidem 6 a vyšším. Vypněte hlídání soukromí pro Gadgetbridge, protože může způsobit nestabilitu mobilu. Pokud není zařízení vyhledáno během několika minut, zkuste to znovu po restartu mobilu. Poznámka: Obraz zařízení Jméno/přezdívka @@ -199,7 +199,7 @@ Z %1$s do %2$s Nosíte vlevo nebo vpravo? Profil vibrací - Staccato + Staccato Krátké Střední Dlouhé @@ -248,19 +248,19 @@ Počet nabití: %s Váš spánek Spánek za týden - Spánek dnes, cíl: %1$s + Spánek dnes, cíl: %1$s Kroky za týden Vaše aktivita a spánek Nahrávám firmware... Soubor nelze nainstalovat, zařízení není připraveno. - Mi Band Firmware %1$s + Mi Band Firmware %1$s Kompatibilní verze Netestovaná verze! Připojení k zařízení: %1$s - Pebble Firmware %1$s + Pebble Firmware %1$s Správná revize HW Revize HW není správná! - %1$s (%2$s) + %1$s (%2$s) Při přenosu firmwaru nastaly potíže. Nerestartujte svůj Mi Band! Problém při přenosu matadat firmware Instalace firmware je kompletní @@ -293,7 +293,7 @@ Časový posun zařízení v hodinách (pro zjišťování spánku směnařů) Mi2: formát data Čas - + Zapnout displej při zvednutí Přenáším data od %1$s čekání na znovupřipojení @@ -304,14 +304,14 @@ Váha v kg ověřování ověřování vyžadováno - Spí... + Spí... Přidat widget Preferovaná doba spánku v hodinách Budík nastaven na %1$02d:%2$02d - HW: %1$s - FW: %1$s + HW: %1$s + FW: %1$s Chyba při vytváření adresáře pro logy: %1$s - Tep: + Tep: Probíhá aktualizace firmware Firmware neodeslán Srdeční tep @@ -350,10 +350,10 @@ Toto vypněte v případě problémů s připojením Metrické Imperiální - 24h - dop./odp. + 24h + dop./odp. Budík - (%1$s) - Nalezeno! - Formát času Mi2 + (%1$s) + Nalezeno! + Formát času Mi2 diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 7fc51617f..36d95ed90 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -84,7 +84,7 @@ Privatsphäre-Modus für Anrufe Zeige Name und Telefonnumer Verstecke den Namen aber zeige die Telefonnummer an - Zeige den Namen an aber verstecke die Telefonnummer + Zeige den Namen an aber verstecke die Telefonnummer Verstecke Name und Telefonnummer App-Benachrichtigungen blockieren Vorgefertigte Nachrichten @@ -364,7 +364,7 @@ (%1$s) Gefunden! Mi2: Uhrzeit-Format - Installiere Version %1$s vor dem Installieren der Firmware! - Text Benachrichtigung - aus + Installiere Version %1$s vor dem Installieren der Firmware! + Text Benachrichtigung + aus diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index eb9eb9a5d..5fd7ba3e5 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -271,8 +271,8 @@ Instalación del firmware completa, reiniciando dispositivo... Falló la escritura del firmware Pasos - Calorías - Distancia + Calorías + Distancia Actividad Pasos hoy, objetivo: %1$s No confirmar transferencia @@ -330,13 +330,13 @@ Borrar la base de datos antigua No se puede acceder a la ruta para exportar . Por favor, contacta con los desarrolladores. Exportado a: %1$s - Error de exportación de la base de datos: %1$s - Error de exportación de las preferencias: %1$s + Error de exportación de la base de datos: %1$s + Error de exportación de las preferencias: %1$s ¿Importar Datos? ¿Quiere sobreescribir la base de datos actual? Todos sus datos actuales (si los hay) se borrarán. Importado con éxito. - Error de importación de la base de datos: %1$s - Error de importación de las preferencias: %1$s + Error de importación de la base de datos: %1$s + Error de importación de las preferencias: %1$s ¿Quieres borrar los datos de actividad? ¿Quieres borrar la base de datos? Todos tus datos de actividad y la información sobre tus dispositivos se borrarán. Datos borrados. @@ -368,13 +368,13 @@ Notificaciones textuales = 1.0.1.28 and Mili_pro.ft* installed.]]> off - Intento de emparejamiento con %1$s - El enlace con %1$s falló instantáneamente - Intentando conectar con: %1$s - Activa el Bluetooth para encontrar dispositivos. - Correctamente conectado con %1$s - Emparejar con %1$s? - Selecciona Emparejar para emparejar tus dispositivos. Si esto falla, prueba de nuevo sin emparejar. - Emparejar - No emparejar + Intento de emparejamiento con %1$s + El enlace con %1$s falló instantáneamente + Intentando conectar con: %1$s + Activa el Bluetooth para encontrar dispositivos. + Correctamente conectado con %1$s + Emparejar con %1$s? + Selecciona Emparejar para emparejar tus dispositivos. Si esto falla, prueba de nuevo sin emparejar. + Emparejar + No emparejar diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 2a196cd88..b49aa815d 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -271,8 +271,8 @@ Installation complète du micrologiciel, redémarrage de l\'appareil Échec lors de l\'écriture du micrologiciel Pas - Calories - Distance + Calories + Distance Activité en direct Nombre de pas aujourd\'hui, objectif: %1$s Ne pas confirmer le transfert de données d\'activités @@ -332,13 +332,13 @@ NOTE: la base de données sera bien évidement plus grande ! Effacer l\'ancienne base de données Impossible d\'accéder au fichier d\'export. Merci de contacter les développeurs. Exporter vers : %1$s - Erreur d\'exportation de la base de données: %1$s - Erreur d\'exportation des préférences: %1$s + Erreur d\'exportation de la base de données: %1$s + Erreur d\'exportation des préférences: %1$s Importer des données ? Voulez-vous vraiment effacer la base de données actuelle ? Toutes vos données (si vous en avez) seront perdues. Importation réussie. - Erreur lors de l\'importation de la base de données: %1$s - Erreur d\'importation des préférences: %1$s + Erreur lors de l\'importation de la base de données: %1$s + Erreur d\'importation des préférences: %1$s Détruire les anciennes données ? Voulez-vous vraiment détruire entièrement la base de données ? Toutes vos données d\'activité et vos informations issues de vos appareils seront perdues. Les données ont été effacées. @@ -370,13 +370,13 @@ NOTE: la base de données sera bien évidement plus grande ! Notifications textuelles off - Tentative d\'appairage avec %1$s - Le lien avec %1$s a échoué instantanément - Tentative de connexion à: %1$s - Activez le Bluetooth pour trouver des dispositifs - Correctement lié à %1$s - Appairer avec %1$s - Sélectionnez Appairer pour appairer vos dispositifs. Si cela échoue, essayez à nouveau sans appairage. - Appairage - Ne pas appairer + Tentative d\'appairage avec %1$s + Le lien avec %1$s a échoué instantanément + Tentative de connexion à: %1$s + Activez le Bluetooth pour trouver des dispositifs + Correctement lié à %1$s + Appairer avec %1$s + Sélectionnez Appairer pour appairer vos dispositifs. Si cela échoue, essayez à nouveau sans appairage. + Appairage + Ne pas appairer diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 818100d3f..27e8a4fc7 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -13,12 +13,12 @@ Rimuovi dispositivo Rimuovi %1$s Il dispositivo verrà rimosso e tutti i dati ad esso associati verranno cancellati! - Apri menu - Chiudi menu - Pressione prolungata sulla scheda per scollegare + Apri menu + Chiudi menu + Pressione prolungata sulla scheda per scollegare Disconnessione Connessione - Screenshot del dispositivo + Screenshot del dispositivo Debug Gestione app @@ -80,11 +80,11 @@ sempre se lo schermo è spento mai - Privacy + Privacy Impostazioni privacy chiamate Mostra nome e numero chiamante Nascondi il nome ma mostra il numero del chiamante - Nascondi il numero ma mostra il nome + Nascondi il numero ma mostra il nome Nascondi nome e numero del chiamante Blocca applicazioni Messaggi preimpostati @@ -350,12 +350,12 @@ Disattiva se hai problemi di connession Metrico Imperiale - 24H - AM/PM + 24H + AM/PM Sveglia Trovato! Mi2: Formato dell\'orario - E\' necessario installare la verione %1$s prima di installare questo firmware! - Notifiche - spento + E\' necessario installare la verione %1$s prima di installare questo firmware! + Notifiche + spento diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 300197140..5cb59124c 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -84,7 +84,7 @@ 通話プライバシーモード 名前と番号を表示 名前は非表示、番号は表示 - 番号は非表示、名前は表示 + 番号は非表示、名前は表示 名前と番号を非表示 アップのブラックリスト 定型のメッセージ @@ -271,8 +271,8 @@ ファームウェアのインストールが完了しました。デバイスを再起動します… ファームウェアの書き込みに失敗しました 歩数 - カロリー - 距離 + カロリー + 距離 生活活動 今日の歩数、目標: %1$s 活動データ転送に応答しない @@ -331,12 +331,12 @@ エクスポート パスにアクセスできません。開発者にご連絡ください。 エクスポートしました: %1$s DB のエクスポート時にエラー: %1$s - 設定のエクスポート時にエラー: %1$s + 設定のエクスポート時にエラー: %1$s データをインポートしますか? 現在のデータベースを上書きしてもよろしいですか? 現在の活動データは (もしあれば) すべて失われます。 インポートに成功しました。 DB のインポート時にエラー: %1$s - 設定のインポート時にエラー: %1$s + 設定のインポート時にエラー: %1$s 活動データを削除しますか? データベース全体を削除してもよろしいですか? 活動データとお使いのデバイスに関する情報がすべて失われます。 データを正常に削除しました。 @@ -364,17 +364,17 @@ (%1$s) 見つかりました! Mi2: 時刻形式 - このファームウェアをインストールする前に、バージョン %1$s をインストールする必要があります! - テキスト通知 - = 1.0.1.28 と Mili_pro.ft* をインストールしていることが必要です。]]> - オフ - %1$s とペアを試みています - %1$s との接続がすぐに失敗しました。 - 接続の試行中: %1$s - デバイスを見つけるため Bluetooth を有効にします。 - %1$s と接続しました。 - %1$sとペアにしますか? - お使いのデバイスとペアにする相手を選択します。 これに失敗した場合は、ペア設定をせずに再試行してください。 - ペア - ペアにしない + このファームウェアをインストールする前に、バージョン %1$s をインストールする必要があります! + テキスト通知 + = 1.0.1.28 と Mili_pro.ft* をインストールしていることが必要です。]]> + オフ + %1$s とペアを試みています + %1$s との接続がすぐに失敗しました。 + 接続の試行中: %1$s + デバイスを見つけるため Bluetooth を有効にします。 + %1$s と接続しました。 + %1$sとペアにしますか? + お使いのデバイスとペアにする相手を選択します。 これに失敗した場合は、ペア設定をせずに再試行してください。 + ペア + ペアにしない diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 2b419873e..2e451f1cf 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -289,7 +289,7 @@ Sobre para transferir dados desde %1$s aguarde para reconectar Sobre você - Ano de anoversário + Ano de anoversário Gênero Altura em cm Peso em kg From 79f3cad36d1f2dc3bd2fab7ddb3fe1d71ac5f2a5 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Mon, 24 Jul 2017 23:57:07 +0200 Subject: [PATCH 25/48] Pebble: some refinements to webview - rename the createWebView method to getInstance - remove the stateChangeListener after it has been fired once and remove obsolete code within - instantiate the jsInterface object only when needed - use the application context when possible to limit the usage of the mutableContextWrapper --- .../activities/BackgroundWebViewActivity.java | 2 +- .../activities/ExternalPebbleJSActivity.java | 19 +------------------ .../gadgetbridge/util/WebViewSingleton.java | 14 ++++++-------- 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java index efb88399c..d903fe0d9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java @@ -9,7 +9,7 @@ public class BackgroundWebViewActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - WebViewSingleton.createWebView(this); + WebViewSingleton.getInstance(this); finish(); } } 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 1f443e968..cc70d4f7c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java @@ -25,7 +25,6 @@ import android.support.v4.app.NavUtils; import android.view.MenuItem; import android.view.View; import android.webkit.JavascriptInterface; -import android.webkit.ValueCallback; import android.webkit.WebView; import android.widget.FrameLayout; import android.widget.Toast; @@ -69,31 +68,15 @@ public class ExternalPebbleJSActivity extends GBActivity { FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder); fl.addView(myWebView); - myWebView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { - v.setLayerType(View.LAYER_TYPE_HARDWARE, null); - //show configuration - moved to JS -// myWebView.evaluateJavascript("Pebble.evaluate('showConfiguration');", new ValueCallback() { -// @Override -// public void onReceiveValue(String s) { -// LOG.debug("Callback from showConfiguration: " + s); -// } -// }); } @Override public void onViewDetachedFromWindow(View v) { - myWebView.removeJavascriptInterface("GBActivity"); - myWebView.setWillNotDraw(true); - myWebView.evaluateJavascript("showStep('step1')", new ValueCallback() { - @Override - public void onReceiveValue(String s) { - LOG.debug("Callback from window detach: " + s); - } - }); + v.removeOnAttachStateChangeListener(this); FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder); fl.removeAllViews(); } 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 70b38f9f4..7042dd34e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -69,7 +69,7 @@ public class WebViewSingleton { private WebViewSingleton() { } - public static synchronized WebView createWebView(Activity context) { + public static synchronized WebView getInstance(Activity context) { if (instance == null) { webViewSingleton.contextWrapper = new MutableContextWrapper(context); webViewSingleton.mainLooper = context.getMainLooper(); @@ -90,7 +90,7 @@ public class WebViewSingleton { } public static void updateActivityContext(Activity context) { - if (context instanceof Activity) { + if (context != null) { webViewSingleton.contextWrapper.setBaseContext(context); } } @@ -101,12 +101,10 @@ public class WebViewSingleton { } public static void runJavascriptInterface(GBDevice device, UUID uuid) { - - final JSInterface jsInterface = new JSInterface(device, uuid); - if (uuid.equals(currentRunningUUID)) { LOG.debug("WEBVIEW uuid not changed keeping the old context"); } else { + final JSInterface jsInterface = new JSInterface(device, uuid); LOG.debug("WEBVIEW uuid changed, restarting"); currentRunningUUID = uuid; new Handler(webViewSingleton.mainLooper).post(new Runnable() { @@ -178,9 +176,9 @@ public class WebViewSingleton { this.timestamp = (System.currentTimeMillis() / 1000) - 86400; //let accessor know this value is really old - if (ActivityCompat.checkSelfPermission(webViewSingleton.contextWrapper, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED && + if (ActivityCompat.checkSelfPermission(GBApplication.getContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED && prefs.getBoolean("use_updated_location_if_available", false)) { - LocationManager locationManager = (LocationManager) webViewSingleton.contextWrapper.getSystemService(Context.LOCATION_SERVICE); + LocationManager locationManager = (LocationManager) GBApplication.getContext().getSystemService(Context.LOCATION_SERVICE); Criteria criteria = new Criteria(); String provider = locationManager.getBestProvider(criteria, false); if (provider != null) { @@ -328,7 +326,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); - webViewSingleton.contextWrapper.startActivity(i); + GBApplication.getContext().startActivity(i); } else { url = url.replaceFirst("^pebblejs://close#", "file:///android_asset/app_config/configure.html?config=true&json="); view.loadUrl(url); From 868593cfd3790517f6b30e01807be726907e8eac Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Wed, 26 Jul 2017 18:12:12 +0200 Subject: [PATCH 26/48] Pebble: further improvements to the background webview - the webview is not static anymore (but its holder is) - the openweathermap calls of type weather and forecast are now supported (as properly as possible) - assemble the main JSON messages in the weather receiver, as info are lost afterwards - switch to Uri parsing instead of String (also unify handling of legacy and new requests) - attempt to address most of the lints/warnings of AS - remove printStackTrace in favor of LOG.warn --- .../gadgetbridge/util/WebViewSingleton.java | 233 ++++++++++-------- .../notification/ParcelableWeather2.java | 113 ++++++++- 2 files changed, 235 insertions(+), 111 deletions(-) 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 7042dd34e..42fe21a35 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -1,6 +1,7 @@ package nodomain.freeyourgadget.gadgetbridge.util; import android.Manifest; +import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; @@ -15,6 +16,7 @@ import android.os.Handler; import android.os.Looper; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; +import android.util.SparseArray; import android.webkit.ConsoleMessage; import android.webkit.JavascriptInterface; import android.webkit.ValueCallback; @@ -47,6 +49,7 @@ import java.security.NoSuchAlgorithmException; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; +import java.util.Map; import java.util.Scanner; import java.util.UUID; @@ -54,13 +57,12 @@ 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; public class WebViewSingleton { private static final Logger LOG = LoggerFactory.getLogger(WebViewSingleton.class); - private static WebView instance = null; + private WebView instance = null; private MutableContextWrapper contextWrapper; private Looper mainLooper; private static WebViewSingleton webViewSingleton = new WebViewSingleton(); @@ -70,23 +72,23 @@ public class WebViewSingleton { } public static synchronized WebView getInstance(Activity context) { - if (instance == null) { + if (webViewSingleton.instance == null) { webViewSingleton.contextWrapper = new MutableContextWrapper(context); webViewSingleton.mainLooper = context.getMainLooper(); - instance = new WebView(webViewSingleton.contextWrapper); - instance.setWillNotDraw(true); - instance.clearCache(true); - instance.setWebViewClient(new GBWebClient()); - instance.setWebChromeClient(new GBChromeClient()); - instance.setWebContentsDebuggingEnabled(true); - WebSettings webSettings = instance.getSettings(); + webViewSingleton.instance = new WebView(webViewSingleton.contextWrapper); + WebView.setWebContentsDebuggingEnabled(true); + webViewSingleton.instance.setWillNotDraw(true); + webViewSingleton.instance.clearCache(true); + webViewSingleton.instance.setWebViewClient(new GBWebClient()); + webViewSingleton.instance.setWebChromeClient(new GBChromeClient()); + WebSettings webSettings = webViewSingleton.instance.getSettings(); webSettings.setJavaScriptEnabled(true); //needed to access the DOM webSettings.setDomStorageEnabled(true); //needed for localstorage webSettings.setDatabaseEnabled(true); } - return instance; + return webViewSingleton.instance; } public static void updateActivityContext(Activity context) { @@ -97,7 +99,7 @@ public class WebViewSingleton { @NonNull public static WebView getWebView() { - return instance; + return webViewSingleton.instance; } public static void runJavascriptInterface(GBDevice device, UUID uuid) { @@ -110,9 +112,9 @@ public class WebViewSingleton { new Handler(webViewSingleton.mainLooper).post(new Runnable() { @Override public void run() { - instance.removeJavascriptInterface("GBjs"); - instance.addJavascriptInterface(jsInterface, "GBjs"); - instance.loadUrl("file:///android_asset/app_config/configure.html?rand=" + Math.random() * 500); + webViewSingleton.instance.removeJavascriptInterface("GBjs"); + webViewSingleton.instance.addJavascriptInterface(jsInterface, "GBjs"); + webViewSingleton.instance.loadUrl("file:///android_asset/app_config/configure.html?rand=" + Math.random() * 500); } }); } @@ -121,7 +123,7 @@ public class WebViewSingleton { public static void appMessage(GBDeviceEventAppMessage message) { - if (instance == null) { + if (webViewSingleton.instance == null) { LOG.warn("WEBVIEW is not initialized, cannot send appMessages to it"); return; } @@ -131,7 +133,7 @@ public class WebViewSingleton { new Handler(webViewSingleton.mainLooper).post(new Runnable() { @Override public void run() { - instance.evaluateJavascript("Pebble.evaluate('appmessage',[" + appMessage + "]);", new ValueCallback() { + webViewSingleton.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 @@ -146,14 +148,14 @@ public class WebViewSingleton { new Handler(webViewSingleton.mainLooper).post(new Runnable() { @Override public void run() { - if (instance != null) { - instance.setWebChromeClient(null); - instance.setWebViewClient(null); - instance.clearHistory(); - instance.clearCache(true); - instance.loadUrl("about:blank"); - instance.freeMemory(); - instance.pauseTimers(); + if (webViewSingleton.instance != null) { + webViewSingleton.instance.setWebChromeClient(null); + webViewSingleton.instance.setWebViewClient(null); + webViewSingleton.instance.clearHistory(); + webViewSingleton.instance.clearCache(true); + webViewSingleton.instance.loadUrl("about:blank"); +// webViewSingleton.instance.freeMemory(); + webViewSingleton.instance.pauseTimers(); // instance.destroy(); // instance = null; // contextWrapper = null; @@ -168,7 +170,7 @@ public class WebViewSingleton { double altitude; float latitude, longitude, accuracy, speed; - public CurrentPosition() { + private CurrentPosition() { Prefs prefs = GBApplication.getPrefs(); this.latitude = prefs.getFloat("location_latitude", 0); this.longitude = prefs.getFloat("location_longitude", 0); @@ -188,15 +190,12 @@ public class WebViewSingleton { this.latitude = (float) lastKnownLocation.getLatitude(); this.longitude = (float) lastKnownLocation.getLongitude(); - this.accuracy = (float) lastKnownLocation.getAccuracy(); + this.accuracy = lastKnownLocation.getAccuracy(); this.altitude = (float) lastKnownLocation.getAltitude(); - this.speed = (float) lastKnownLocation.getSpeed(); - + this.speed = lastKnownLocation.getSpeed(); } } } - - } } @@ -204,60 +203,79 @@ public class WebViewSingleton { return null; } + private static JSONObject coordObject(CurrentPosition currentPosition) throws JSONException { + JSONObject coord = new JSONObject(); + coord.put("lat", currentPosition.latitude); + coord.put("lon", currentPosition.longitude); + return coord; + } - private static WebResourceResponse mimicOpenWeatherMapResponse(String origialRequestURL) { - WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec(); + private static JSONObject sysObject(CurrentPosition currentPosition) throws JSONException { + GregorianCalendar[] sunrise = SPA.calculateSunriseTransitSet(new GregorianCalendar(), currentPosition.latitude, currentPosition.longitude, DeltaT.estimate(new GregorianCalendar())); - if (weatherSpec == null) { + JSONObject sys = new JSONObject(); + sys.put("country", "World"); + sys.put("sunrise", (sunrise[0].getTimeInMillis() / 1000)); + sys.put("sunset", (sunrise[2].getTimeInMillis() / 1000)); + + return sys; + } + + private static void convertTemps(JSONObject main, String units) throws JSONException { + if ("metric".equals(units)) { + main.put("temp", (int) main.get("temp") - 273); + main.put("temp_min", (int) main.get("temp_min") - 273); + main.put("temp_max", (int) main.get("temp_max") - 273); + } else if ("imperial".equals(units)) { //it's 2017... this is so sad + main.put("temp", ((int) (main.get("temp")) - 273.15f) * 1.8f + 32); + main.put("temp_min", ((int) (main.get("temp_min")) - 273.15f) * 1.8f + 32); + main.put("temp_max", ((int) (main.get("temp_max")) - 273.15f) * 1.8f + 32); + } + } + + private static WebResourceResponse mimicOpenWeatherMapResponse(String type, String units) { + + if (Weather.getInstance() == null || Weather.getInstance().getWeather2() == null) { LOG.warn("WEBVIEW - Weather instance is null, cannot update weather"); return null; } - //location block CurrentPosition currentPosition = new CurrentPosition(); - GregorianCalendar[] sunrise = SPA.calculateSunriseTransitSet(new GregorianCalendar(), currentPosition.latitude, currentPosition.longitude, DeltaT.estimate(new GregorianCalendar())); - JSONObject resp = new JSONObject(); - JSONObject coord = new JSONObject(); - JSONObject sys = new JSONObject(); - JSONArray weather = new JSONArray(); - JSONObject currCond = new JSONObject(); - JSONObject main = new JSONObject(); try { - coord.put("lat", currentPosition.latitude); - coord.put("lon", currentPosition.longitude); + JSONObject resp; - sys.put("country", "World"); - sys.put("sunrise", (sunrise[0].getTimeInMillis() / 1000)); - sys.put("sunset", (sunrise[2].getTimeInMillis() / 1000)); + if ("weather".equals(type) && Weather.getInstance().getWeather2().reconstructedWeather != null) { + resp = new JSONObject(Weather.getInstance().getWeather2().reconstructedWeather.toString()); - currCond.put("id", weatherSpec.currentConditionCode); - currCond.put("main", weatherSpec.currentCondition); - currCond.put("icon", Weather.mapToOpenWeatherMapIcon(weatherSpec.currentConditionCode)); - weather.put(currCond); + JSONObject main = resp.getJSONObject("main"); - int currentTemp = weatherSpec.currentTemp; - int todayMinTemp = weatherSpec.todayMinTemp; - int todayMaxTemp = weatherSpec.todayMaxTemp; + convertTemps(main, units); //caller might want different units - if (origialRequestURL.contains("units=metric")) { // not nice but WebResourceRequest is not in KitKat - currentTemp -= 273; - todayMinTemp -= 273; - todayMaxTemp -= 273; + resp.put("cod", 200); + resp.put("coord", coordObject(currentPosition)); + resp.put("sys", sysObject(currentPosition)); + } else if ("forecast".equals(type) && Weather.getInstance().getWeather2().reconstructedForecast != null) { + resp = new JSONObject(Weather.getInstance().getWeather2().reconstructedForecast.toString()); + + JSONObject city = resp.getJSONObject("city"); + city.put("coord", coordObject(currentPosition)); + + JSONArray list = resp.getJSONArray("list"); + for (int i = 0, size = list.length(); i < size; i++) { + JSONObject item = list.getJSONObject(i); + JSONObject main = item.getJSONObject("main"); + convertTemps(main, units); //caller might want different units + } + + resp.put("cod", 200); + } else { + LOG.warn("WEBVIEW - cannot mimick request of type " + type + " (unsupported or lack of data)"); + return null; } - main.put("temp", currentTemp); - main.put("temp_min", todayMinTemp); - main.put("temp_max", todayMaxTemp); - main.put("name", weatherSpec.location); - - resp.put("cod", 200); - resp.put("coord", coord); - resp.put("sys", sys); - resp.put("weather", weather); - resp.put("main", main); LOG.info("WEBVIEW - mimic openweather response" + resp.toString()); - HashMap headers = new HashMap<>(); + Map headers = new HashMap<>(); headers.put("Access-Control-Allow-Origin", "*"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { @@ -269,7 +287,7 @@ public class WebViewSingleton { return new WebResourceResponse("application/json", "utf-8", new ByteArrayInputStream(resp.toString().getBytes())); } } catch (JSONException e) { - e.printStackTrace(); + LOG.warn(e.getMessage()); } return null; @@ -297,39 +315,50 @@ public class WebViewSingleton { } private static class GBWebClient extends WebViewClient { + @TargetApi(Build.VERSION_CODES.LOLLIPOP) @Override public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { - 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(request.getUrl().toString()); - } else { - LOG.debug("WEBVIEW request:" + request.getUrl().toString() + " not intercepted"); - } - } + Uri parsedUri = request.getUrl(); + LOG.debug("WEBVIEW shouldInterceptRequest URL: " + parsedUri.toString()); + WebResourceResponse mimickedReply = mimicReply(parsedUri); + if (mimickedReply != null) + return mimickedReply; return super.shouldInterceptRequest(view, request); } @Override public WebResourceResponse shouldInterceptRequest(WebView view, String url) { - LOG.debug("WEBVIEW shouldInterceptRequest URL (legacy)" + url); - if (url.startsWith("http://api.openweathermap.org") || url.startsWith("https://api.openweathermap.org")) { - return mimicOpenWeatherMapResponse(url); - } else { - LOG.debug("WEBVIEW request:" + url + " not intercepted"); - } + LOG.debug("WEBVIEW shouldInterceptRequest URL (legacy): " + url); + Uri parsedUri = Uri.parse(url); + WebResourceResponse mimickedReply = mimicReply(parsedUri); + if (mimickedReply != null) + return mimickedReply; return super.shouldInterceptRequest(view, url); } + private WebResourceResponse mimicReply(Uri requestedUri) { + if (requestedUri.getHost().contains("openweathermap.org")) { + LOG.debug("WEBVIEW request to openweathermap.org detected of type: " + requestedUri.getLastPathSegment() + " params: " + requestedUri.getQuery()); + return mimicOpenWeatherMapResponse(requestedUri.getLastPathSegment(), requestedUri.getQueryParameter("units")); + } else { + LOG.debug("WEBVIEW request:" + requestedUri.toString() + " not intercepted"); + } + return null; + } + @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { - if (url.startsWith("http://") || url.startsWith("https://")) { + Uri parsedUri = Uri.parse(url); + + if (parsedUri.getScheme().startsWith("http")) { Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); GBApplication.getContext().startActivity(i); - } else { + } else if (parsedUri.getScheme().startsWith("pebblejs")) { url = url.replaceFirst("^pebblejs://close#", "file:///android_asset/app_config/configure.html?config=true&json="); view.loadUrl(url); + } else { + LOG.debug("WEBVIEW Ignoring unhandled scheme: " + parsedUri.getScheme()); } return true; @@ -341,21 +370,21 @@ public class WebViewSingleton { File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); File configurationFile = new File(destDir, uuid.toString() + ".json"); if (configurationFile.exists()) { - String jsonstring = FileUtils.getStringFromFile(configurationFile); - JSONObject json = new JSONObject(jsonstring); + String jsonString = FileUtils.getStringFromFile(configurationFile); + JSONObject json = new JSONObject(jsonString); return json.getJSONObject("appKeys"); } } catch (IOException | JSONException e) { - e.printStackTrace(); + LOG.warn(e.getMessage()); } return null; } - public static String parseIncomingAppMessage(String msg, UUID uuid) { + private static String parseIncomingAppMessage(String msg, UUID uuid) { JSONObject jsAppMessage = new JSONObject(); JSONObject knownKeys = getAppConfigurationKeys(uuid); - HashMap appKeysMap = new HashMap(); + SparseArray appKeysMap = new SparseArray<>(); String inKey, outKey; //knownKeys contains "name"->"index", we need to reverse that @@ -388,7 +417,7 @@ public class WebViewSingleton { jsAppMessage.put("payload", outgoing); } catch (JSONException e) { - e.printStackTrace(); + LOG.warn(e.getMessage()); } return jsAppMessage.toString(); } @@ -398,7 +427,7 @@ public class WebViewSingleton { UUID mUuid; GBDevice device; - public JSInterface(GBDevice device, UUID mUuid) { + private JSInterface(GBDevice device, UUID mUuid) { LOG.debug("Creating JS interface for UUID: " + mUuid.toString()); this.device = device; this.mUuid = mUuid; @@ -456,7 +485,7 @@ public class WebViewSingleton { GBApplication.deviceService().onAppConfiguration(this.mUuid, out.toString()); } catch (JSONException e) { - e.printStackTrace(); + LOG.warn(e.getMessage()); } } @@ -470,7 +499,7 @@ public class WebViewSingleton { //TODO: use real info wi.put("language", "en"); } catch (JSONException e) { - e.printStackTrace(); + LOG.warn(e.getMessage()); } //Json not supported apparently, we need to cast back and forth return wi.toString(); @@ -486,7 +515,7 @@ public class WebViewSingleton { return "file:///" + configurationFile.getAbsolutePath(); } } catch (IOException e) { - e.printStackTrace(); + LOG.warn(e.getMessage()); } return null; } @@ -501,7 +530,7 @@ public class WebViewSingleton { } } catch (IOException e) { GB.toast("Error reading presets", Toast.LENGTH_LONG, GB.ERROR); - e.printStackTrace(); + LOG.warn(e.getMessage()); } return null; } @@ -519,7 +548,7 @@ public class WebViewSingleton { GB.toast("Presets stored", Toast.LENGTH_SHORT, GB.INFO); } catch (IOException e) { GB.toast("Error storing presets", Toast.LENGTH_LONG, GB.ERROR); - e.printStackTrace(); + LOG.warn(e.getMessage()); } } @@ -537,12 +566,12 @@ public class WebViewSingleton { digest.update(bytes, 0, bytes.length); bytes = digest.digest(); final StringBuilder sb = new StringBuilder(); - for (int i = 0; i < bytes.length; i++) { - sb.append(String.format("%02X", bytes[i])); + for (byte aByte : bytes) { + sb.append(String.format("%02X", aByte)); } return sb.toString().toLowerCase(); } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { - e.printStackTrace(); + LOG.warn(e.getMessage()); return prefix; } } @@ -577,7 +606,7 @@ public class WebViewSingleton { geoPosition.put("coords", coords); } catch (JSONException e) { - e.printStackTrace(); + LOG.warn(e.getMessage()); } LOG.info("WEBVIEW - geo position" + geoPosition.toString()); return geoPosition.toString(); diff --git a/app/src/main/java/ru/gelin/android/weather/notification/ParcelableWeather2.java b/app/src/main/java/ru/gelin/android/weather/notification/ParcelableWeather2.java index 2911885c1..70870a08d 100644 --- a/app/src/main/java/ru/gelin/android/weather/notification/ParcelableWeather2.java +++ b/app/src/main/java/ru/gelin/android/weather/notification/ParcelableWeather2.java @@ -20,9 +20,14 @@ import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import nodomain.freeyourgadget.gadgetbridge.model.Weather; + public class ParcelableWeather2 implements Parcelable { private static final Logger LOG = LoggerFactory.getLogger(ParcelableWeather2.class); @@ -35,15 +40,17 @@ public class ParcelableWeather2 implements Parcelable { public int currentTemp = 0; public String currentCondition = ""; - String[] currentConditionType = null; + private String[] currentConditionType = null; public int currentConditionCode = 3200; - String[] forecastConditionType = null; + private String[] forecastConditionType = null; public int forecastConditionCode = 3200; public int todayLowTemp = 0; public int todayHighTemp = 0; public int forecastLowTemp = 0; public int forecastHighTemp = 0; + public JSONObject reconstructedWeather = null; + public JSONObject reconstructedForecast = null; private ParcelableWeather2(Parcel in) { int version = in.readInt(); @@ -59,6 +66,11 @@ public class ParcelableWeather2 implements Parcelable { int conditions = bundle.getInt("weather_conditions"); if (conditions > 0) { Bundle conditionBundle = in.readBundle(); + reconstructedWeather = new JSONObject(); + JSONArray weather = new JSONArray(); + JSONObject condition = new JSONObject(); + JSONObject main = new JSONObject(); + currentCondition = conditionBundle.getString("weather_condition_text"); conditionBundle.getStringArray("weather_condition_types"); currentTemp = conditionBundle.getInt("weather_current_temp"); @@ -67,22 +79,105 @@ public class ParcelableWeather2 implements Parcelable { currentConditionCode = weatherConditionTypesToOpenWeatherMapIds(currentConditionType[0]); todayLowTemp = conditionBundle.getInt("weather_low_temp"); todayHighTemp = conditionBundle.getInt("weather_high_temp"); + try { + condition.put("id", currentConditionCode); + condition.put("main", currentCondition); + condition.put("icon", Weather.mapToOpenWeatherMapIcon(currentConditionCode)); + weather.put(condition); + + main.put("temp", currentTemp); + main.put("humidity", conditionBundle.getInt("weather_humidity_value")); + main.put("temp_min", todayLowTemp); + main.put("temp_max", todayHighTemp); + main.put("name", location); + + reconstructedWeather.put("weather", weather); + reconstructedWeather.put("main", main); + + } catch (JSONException e) { + e.printStackTrace(); + } + LOG.debug("Weather JSON for WEBVIEW: " + reconstructedWeather.toString()); //fetch immediate next forecast if (--conditions > 0) { + int timeOffset = 86400000; //manually determined + reconstructedForecast = new JSONObject(); + JSONArray list = new JSONArray(); + JSONObject city = new JSONObject(); + JSONObject item = new JSONObject(); + condition = new JSONObject(); + main = new JSONObject(); + weather = new JSONArray(); Bundle forecastBundle = in.readBundle(); forecastConditionType = forecastBundle.getStringArray("weather_condition_types"); forecastConditionCode = weatherConditionTypesToOpenWeatherMapIds(forecastConditionType[0]); forecastLowTemp = forecastBundle.getInt("weather_low_temp"); forecastHighTemp = forecastBundle.getInt("weather_high_temp"); + try { + condition.put("id", forecastConditionCode); + condition.put("main", forecastBundle.getString("weather_condition_text")); + condition.put("icon", Weather.mapToOpenWeatherMapIcon(forecastConditionCode)); + weather.put(condition); + + main.put("temp", forecastBundle.getInt("weather_current_temp")); + main.put("humidity", forecastBundle.getInt("weather_humidity_value")); + main.put("temp_min", forecastLowTemp); + main.put("temp_max", forecastHighTemp); + + //forecast + //"city":{"id":3181913,"name":"Bolzano","coord":{"lat":46.4927,"lon":11.3336},"country":"IT"} + city.put("name", location); + city.put("country", "World"); + reconstructedForecast.put("city", city); + + item.put("dt", (time / 1000) + timeOffset); + item.put("main", main); + item.put("weather", weather); + list.put(item); + } catch (JSONException e) { + e.printStackTrace(); + } + + // get the rest + while (--conditions > 0) { + conditionBundle = in.readBundle(); + conditionBundle.getString("weather_condition_text"); + weatherConditionTypesToOpenWeatherMapIds(conditionBundle.getStringArray("weather_condition_types")[0]); + conditionBundle.getInt("weather_current_temp"); + item = new JSONObject(); + condition = new JSONObject(); + main = new JSONObject(); + weather = new JSONArray(); + timeOffset += 86400000; + try { + condition.put("id", weatherConditionTypesToOpenWeatherMapIds(conditionBundle.getStringArray("weather_condition_types")[0])); + condition.put("main", conditionBundle.getString("weather_condition_text")); + condition.put("icon", Weather.mapToOpenWeatherMapIcon(weatherConditionTypesToOpenWeatherMapIds(conditionBundle.getStringArray("weather_condition_types")[0]))); + weather.put(condition); + + main.put("temp", conditionBundle.getInt("weather_current_temp")); + main.put("humidity", conditionBundle.getInt("weather_humidity_value")); + main.put("temp_min", conditionBundle.getInt("weather_low_temp")); + main.put("temp_max", conditionBundle.getInt("weather_high_temp")); + + item.put("dt", (time / 1000) + timeOffset); + item.put("main", main); + item.put("weather", weather); + list.put(item); + } catch (JSONException e) { + e.printStackTrace(); + } + } + try { + reconstructedForecast.put("cnt", list.length()); + reconstructedForecast.put("list", list); + + } catch (JSONException e) { + e.printStackTrace(); + } + LOG.debug("Forecast JSON for WEBVIEW: " + reconstructedForecast.toString()); } } - // get the rest - while (--conditions > 0) { - Bundle conditionBundle = in.readBundle(); - conditionBundle.getString("weather_condition_text"); - conditionBundle.getStringArray("weather_condition_types"); - conditionBundle.getInt("weather_current_temp"); - } } public static final Creator CREATOR = new Creator() { From 25e6af81a44d347d0ed57153075fadd6d75fbedf Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Fri, 28 Jul 2017 18:30:27 +0200 Subject: [PATCH 27/48] Pebble: some more fixes to the webview - add back support for clay that was mistakenly removed - timestamps are in milliseconds in JS as well - intercept the whole URL path instead of the last part --- .../gadgetbridge/util/WebViewSingleton.java | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) 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 42fe21a35..43fad24c7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -176,7 +176,7 @@ public class WebViewSingleton { this.longitude = prefs.getFloat("location_longitude", 0); LOG.info("got longitude/latitude from preferences: " + latitude + "/" + longitude); - this.timestamp = (System.currentTimeMillis() / 1000) - 86400; //let accessor know this value is really old + this.timestamp = System.currentTimeMillis() - 86400000; //let accessor know this value is really old if (ActivityCompat.checkSelfPermission(GBApplication.getContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED && prefs.getBoolean("use_updated_location_if_available", false)) { @@ -245,7 +245,7 @@ public class WebViewSingleton { try { JSONObject resp; - if ("weather".equals(type) && Weather.getInstance().getWeather2().reconstructedWeather != null) { + if ("/data/2.5/weather".equals(type) && Weather.getInstance().getWeather2().reconstructedWeather != null) { resp = new JSONObject(Weather.getInstance().getWeather2().reconstructedWeather.toString()); JSONObject main = resp.getJSONObject("main"); @@ -255,7 +255,7 @@ public class WebViewSingleton { resp.put("cod", 200); resp.put("coord", coordObject(currentPosition)); resp.put("sys", sysObject(currentPosition)); - } else if ("forecast".equals(type) && Weather.getInstance().getWeather2().reconstructedForecast != null) { + } else if ("/data/2.5/forecast".equals(type) && Weather.getInstance().getWeather2().reconstructedForecast != null) { //this is wrong, as we only have daily data. Unfortunately it looks like daily forecasts cannot be reconstructed resp = new JSONObject(Weather.getInstance().getWeather2().reconstructedForecast.toString()); JSONObject city = resp.getJSONObject("city"); @@ -337,9 +337,9 @@ public class WebViewSingleton { } private WebResourceResponse mimicReply(Uri requestedUri) { - if (requestedUri.getHost().contains("openweathermap.org")) { - LOG.debug("WEBVIEW request to openweathermap.org detected of type: " + requestedUri.getLastPathSegment() + " params: " + requestedUri.getQuery()); - return mimicOpenWeatherMapResponse(requestedUri.getLastPathSegment(), requestedUri.getQueryParameter("units")); + if (requestedUri.getHost() != null && requestedUri.getHost().contains("openweathermap.org")) { + LOG.debug("WEBVIEW request to openweathermap.org detected of type: " + requestedUri.getPath() + " params: " + requestedUri.getQuery()); + return mimicOpenWeatherMapResponse(requestedUri.getPath(), requestedUri.getQueryParameter("units")); } else { LOG.debug("WEBVIEW request:" + requestedUri.toString() + " not intercepted"); } @@ -357,8 +357,10 @@ public class WebViewSingleton { } else if (parsedUri.getScheme().startsWith("pebblejs")) { url = url.replaceFirst("^pebblejs://close#", "file:///android_asset/app_config/configure.html?config=true&json="); view.loadUrl(url); + } else if (parsedUri.getScheme().equals("data")) { //clay + view.loadUrl(url); } else { - LOG.debug("WEBVIEW Ignoring unhandled scheme: " + parsedUri.getScheme()); + LOG.debug("WEBVIEW Ignoring unhandled scheme: " + parsedUri.getScheme()); } return true; @@ -481,7 +483,7 @@ public class WebViewSingleton { } } - LOG.info("WEBV:" + out.toString()); + LOG.info("WEBVIEW message to pebble: " + out.toString()); GBApplication.deviceService().onAppConfiguration(this.mUuid, out.toString()); } catch (JSONException e) { From c81a768000cc1d8c60c449d3dbe9ee878c4170e6 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Fri, 28 Jul 2017 18:34:36 +0200 Subject: [PATCH 28/48] Pebble: call the callbacks, do not return them --- .../main/assets/app_config/js/gadgetbridge_boilerplate.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 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 976c69c6e..e968422bf 100644 --- a/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js +++ b/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js @@ -161,11 +161,15 @@ function gbPebble() { } else { //pass them silently GBjs.sendAppMessage(JSON.stringify(dict)); } - return callbackAck; + if (callbackAck != undefined) { + callbackAck(); + } } catch (e) { GBjs.gbLog("sendAppMessage failed"); - return callbackNack; + if (callbackNack != undefined) { + callbackNack(); + } } } From 3b35bde42c1ee787141a81d312418fe2f6ba30a1 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Sun, 30 Jul 2017 23:27:49 +0200 Subject: [PATCH 29/48] Pebble: Pass booleans from Javascript Appmessage as such (missing parts from c2af2dd15cabec9edfca6e3471cc3093d783f92c in master) --- .../freeyourgadget/gadgetbridge/util/WebViewSingleton.java | 3 --- 1 file changed, 3 deletions(-) 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 43fad24c7..2c35bd70e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -474,9 +474,6 @@ public class WebViewSingleton { if (passKey) { Object obj = in.get(inKey); - if (obj instanceof Boolean) { - obj = ((Boolean) obj) ? "true" : "false"; - } out.put(outKey, obj); } else { GB.toast("Discarded key " + inKey + ", not found in the local configuration and is not an integer key.", Toast.LENGTH_SHORT, GB.WARN); From ceec76b4f67ab39bedbbf1d028d9e4daaa81d9a1 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Tue, 1 Aug 2017 00:03:28 +0200 Subject: [PATCH 30/48] Pebble: preparations for js appmessage ack/nack callbacks --- .../deviceevents/GBDeviceEventAppMessage.java | 5 +++ .../gadgetbridge/devices/EventHandler.java | 2 +- .../gadgetbridge/impl/GBDeviceService.java | 6 +++- .../gadgetbridge/model/DeviceService.java | 1 + .../service/DeviceCommunicationService.java | 7 +++- .../service/ServiceDeviceSupport.java | 4 +-- .../service/devices/hplus/HPlusSupport.java | 2 +- .../devices/liveview/LiveviewSupport.java | 2 +- .../service/devices/miband/MiBandSupport.java | 2 +- .../devices/miband2/MiBand2Support.java | 2 +- .../pebble/AppMessageHandlerHealthify.java | 2 +- .../pebble/AppMessageHandlerMarioTime.java | 2 +- .../pebble/AppMessageHandlerMorpheuz.java | 2 +- .../pebble/AppMessageHandlerObsidian.java | 2 +- .../pebble/AppMessageHandlerPebStyle.java | 2 +- .../pebble/AppMessageHandlerSquare.java | 2 +- .../AppMessageHandlerTimeStylePebble.java | 2 +- .../pebble/AppMessageHandlerTrekVolle.java | 2 +- .../pebble/AppMessageHandlerZalewszczak.java | 2 +- .../devices/pebble/PebbleIoThread.java | 6 ++-- .../devices/pebble/PebbleProtocol.java | 32 ++++++++++++++----- .../service/devices/pebble/PebbleSupport.java | 4 +-- .../vibratissimo/VibratissimoSupport.java | 2 +- .../gadgetbridge/util/WebViewSingleton.java | 7 +++- .../service/TestDeviceSupport.java | 2 +- 25 files changed, 71 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppMessage.java index c16840a2b..2dd3f3dd9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppMessage.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventAppMessage.java @@ -19,6 +19,11 @@ package nodomain.freeyourgadget.gadgetbridge.deviceevents; import java.util.UUID; public class GBDeviceEventAppMessage extends GBDeviceEvent { + public static int TYPE_APPMESSAGE = 0; + public static int TYPE_ACK = 1; + public static int TYPE_NACK = 2; + + public int type; public UUID appUUID; public int id; public String message; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java index c2cea6fc7..7d72f401e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java @@ -63,7 +63,7 @@ public interface EventHandler { void onAppDelete(UUID uuid); - void onAppConfiguration(UUID appUuid, String config); + void onAppConfiguration(UUID appUuid, String config, Integer id); void onAppReorder(UUID uuids[]); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java index 6fd8f550c..578b5098e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java @@ -251,10 +251,14 @@ public class GBDeviceService implements DeviceService { } @Override - public void onAppConfiguration(UUID uuid, String config) { + public void onAppConfiguration(UUID uuid, String config, Integer id) { Intent intent = createIntent().setAction(ACTION_APP_CONFIGURE) .putExtra(EXTRA_APP_UUID, uuid) .putExtra(EXTRA_APP_CONFIG, config); + + if (id != null) { + intent.putExtra(EXTRA_APP_CONFIG_ID, id); + } invokeService(intent); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java index 033422a33..f89ed766c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java @@ -94,6 +94,7 @@ public interface DeviceService extends EventHandler { String EXTRA_APP_UUID = "app_uuid"; String EXTRA_APP_START = "app_start"; String EXTRA_APP_CONFIG = "app_config"; + String EXTRA_APP_CONFIG_ID = "app_config_id"; String EXTRA_URI = "uri"; String EXTRA_CONFIG = "config"; String EXTRA_ALARMS = "alarms"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java index 067febb52..cf4d77664 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -105,6 +105,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ST import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_TEST_NEW_FUNCTION; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_ALARMS; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_CONFIG; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_CONFIG_ID; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_START; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_UUID; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_BOOLEAN_ENABLE; @@ -479,7 +480,11 @@ public class DeviceCommunicationService extends Service implements SharedPrefere case ACTION_APP_CONFIGURE: { UUID uuid = (UUID) intent.getSerializableExtra(EXTRA_APP_UUID); String config = intent.getStringExtra(EXTRA_APP_CONFIG); - mDeviceSupport.onAppConfiguration(uuid, config); + Integer id = null; + if (intent.hasExtra(EXTRA_APP_CONFIG_ID)) { + id = intent.getIntExtra(EXTRA_APP_CONFIG_ID, 0); + } + mDeviceSupport.onAppConfiguration(uuid, config, id); break; } case ACTION_APP_REORDER: { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java index 26b01e207..a1b7f67de 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java @@ -232,11 +232,11 @@ public class ServiceDeviceSupport implements DeviceSupport { } @Override - public void onAppConfiguration(UUID uuid, String config) { + public void onAppConfiguration(UUID uuid, String config, Integer id) { if (checkBusy("app configuration")) { return; } - delegate.onAppConfiguration(uuid, config); + delegate.onAppConfiguration(uuid, config, id); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java index 843845cce..222d97a69 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java @@ -528,7 +528,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { } @Override - public void onAppConfiguration(UUID appUuid, String config) { + public void onAppConfiguration(UUID appUuid, String config, Integer id) { } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewSupport.java index 5d6246b87..77cf63f68 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/liveview/LiveviewSupport.java @@ -60,7 +60,7 @@ public class LiveviewSupport extends AbstractSerialDeviceSupport { } @Override - public void onAppConfiguration(UUID uuid, String config) { + public void onAppConfiguration(UUID uuid, String config, Integer id) { //nothing to do ATM } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java index 694e80072..7b988b733 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java @@ -844,7 +844,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { } @Override - public void onAppConfiguration(UUID uuid, String config) { + public void onAppConfiguration(UUID uuid, String config, Integer id) { // not supported } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java index 3307e7660..9d860e6b2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java @@ -762,7 +762,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { } @Override - public void onAppConfiguration(UUID uuid, String config) { + public void onAppConfiguration(UUID uuid, String config, Integer id) { // not supported } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerHealthify.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerHealthify.java index a559ee26b..c4d8b692d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerHealthify.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerHealthify.java @@ -58,7 +58,7 @@ class AppMessageHandlerHealthify extends AppMessageHandler { ArrayList> pairs = new ArrayList<>(2); pairs.add(new Pair<>(KEY_CONDITIONS, (Object) weatherSpec.currentCondition)); pairs.add(new Pair<>(KEY_TEMPERATURE, (Object) (weatherSpec.currentTemp - 273))); - byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); + byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null); ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMarioTime.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMarioTime.java index 2ac94ca35..aa776388f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMarioTime.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMarioTime.java @@ -44,7 +44,7 @@ class AppMessageHandlerMarioTime extends AppMessageHandler { ArrayList> pairs = new ArrayList<>(2); pairs.add(new Pair<>(KEY_WEATHER_ICON_ID, (Object) (byte) 1)); pairs.add(new Pair<>(KEY_WEATHER_TEMPERATURE, (Object) (byte) (weatherSpec.currentTemp - 273))); - byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); + byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null); ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMorpheuz.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMorpheuz.java index 0057f8343..b97ad3563 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMorpheuz.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerMorpheuz.java @@ -96,7 +96,7 @@ class AppMessageHandlerMorpheuz extends AppMessageHandler { ArrayList> pairs = new ArrayList<>(); pairs.add(new Pair(key, value)); - return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); + return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerObsidian.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerObsidian.java index 9475c4d11..adbf89a2f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerObsidian.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerObsidian.java @@ -155,7 +155,7 @@ class AppMessageHandlerObsidian extends AppMessageHandler { pairs.add(new Pair<>(messageKeys.get("MSG_KEY_WEATHER_ICON"), (Object) getIconForConditionCode(weatherSpec.currentConditionCode, isNight))); //celsius pairs.add(new Pair<>(messageKeys.get("MSG_KEY_WEATHER_TEMP"), (Object) (weatherSpec.currentTemp - 273))); - return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); + return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerPebStyle.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerPebStyle.java index 712feaa52..f2add3eb4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerPebStyle.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerPebStyle.java @@ -100,7 +100,7 @@ class AppMessageHandlerPebStyle extends AppMessageHandler { pairs.add(new Pair<>(KEY_WEATHER_TEMP, (Object) (weather.currentTemp - 273))); } - byte[] testMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); + byte[] testMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null); ByteBuffer buf = ByteBuffer.allocate(testMessage.length); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerSquare.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerSquare.java index ac95f884b..1c5fd489f 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerSquare.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerSquare.java @@ -67,7 +67,7 @@ class AppMessageHandlerSquare extends AppMessageHandler { pairs.add(new Pair<>(CfgKeyUseCelsius, (Object) 1)); pairs.add(new Pair<>(CfgKeyCelsiusTemperature, (Object) (weatherSpec.currentTemp - 273))); pairs.add(new Pair<>(CfgKeyWeatherLocation, (Object) (weatherSpec.location))); - byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); + byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null); ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTimeStylePebble.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTimeStylePebble.java index cd1295104..243081a6e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTimeStylePebble.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTimeStylePebble.java @@ -145,7 +145,7 @@ class AppMessageHandlerTimeStylePebble extends AppMessageHandler { pairs.add(new Pair<>(messageKeys.get("WeatherForecastHighTemp"), (Object) (weatherSpec.todayMaxTemp - 273))); pairs.add(new Pair<>(messageKeys.get("WeatherForecastLowTemp"), (Object) (weatherSpec.todayMinTemp - 273))); - return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); + return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTrekVolle.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTrekVolle.java index 6ee94bc09..e00b2c5fd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTrekVolle.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerTrekVolle.java @@ -91,7 +91,7 @@ class AppMessageHandlerTrekVolle extends AppMessageHandler { pairs.add(new Pair<>(MESSAGE_KEY_WEATHER_LOCATION, (Object) weatherSpec.location)); - return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); + return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerZalewszczak.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerZalewszczak.java index bde7d7772..2c48ce707 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerZalewszczak.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerZalewszczak.java @@ -72,7 +72,7 @@ class AppMessageHandlerZalewszczak extends AppMessageHandler { ArrayList> pairs = new ArrayList<>(2); pairs.add(new Pair(KEY_TEMP, weatherSpec.currentTemp - 273 + "C")); pairs.add(new Pair(KEY_ICON, getIconForConditionCode(weatherSpec.currentConditionCode))); - byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); + byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs, null); ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length); 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 ab4350723..41ebb4ea6 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 @@ -100,7 +100,9 @@ class PebbleIoThread extends GBDeviceIoThread { private void sendAppMessageJS(GBDeviceEventAppMessage appMessage) { WebViewSingleton.appMessage(appMessage); - write(mPebbleProtocol.encodeApplicationMessageAck(appMessage.appUUID, (byte) appMessage.id)); + if (appMessage.type == GBDeviceEventAppMessage.TYPE_APPMESSAGE) { + write(mPebbleProtocol.encodeApplicationMessageAck(appMessage.appUUID, (byte) appMessage.id)); + } } PebbleIoThread(PebbleSupport pebbleSupport, GBDevice gbDevice, GBDeviceProtocol gbDeviceProtocol, BluetoothAdapter btAdapter, Context context) { @@ -519,7 +521,7 @@ class PebbleIoThread extends GBDeviceIoThread { sendAppMessageJS((GBDeviceEventAppMessage) deviceEvent); if (mEnablePebblekit) { LOG.info("Got AppMessage event"); - if (mPebbleKitSupport != null) { + if (mPebbleKitSupport != null && ((GBDeviceEventAppMessage) deviceEvent).type == GBDeviceEventAppMessage.TYPE_APPMESSAGE) { mPebbleKitSupport.sendAppMessageIntent((GBDeviceEventAppMessage) deviceEvent); } } 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 8484fc908..7c3645681 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 @@ -417,6 +417,8 @@ public class PebbleProtocol extends GBDeviceProtocol { private final HashMap mDatalogSessions = new HashMap<>(); + private Integer[] idLookup = new Integer[256]; + private byte[] encodeSimpleMessage(short endpoint, byte command) { final short LENGTH_SIMPLEMESSAGE = 1; ByteBuffer buf = ByteBuffer.allocate(LENGTH_PREFIX + LENGTH_SIMPLEMESSAGE); @@ -1465,7 +1467,7 @@ public class PebbleProtocol extends GBDeviceProtocol { ArrayList> pairs = new ArrayList<>(); int param = start ? 1 : 0; pairs.add(new Pair<>(1, (Object) param)); - return encodeApplicationMessagePush(ENDPOINT_LAUNCHER, uuid, pairs); + return encodeApplicationMessagePush(ENDPOINT_LAUNCHER, uuid, pairs, null); } } @@ -1898,7 +1900,7 @@ public class PebbleProtocol extends GBDeviceProtocol { return new GBDeviceEvent[]{appMessage, sendBytesAck}; } - byte[] encodeApplicationMessagePush(short endpoint, UUID uuid, ArrayList> pairs) { + byte[] encodeApplicationMessagePush(short endpoint, UUID uuid, ArrayList> pairs, Integer ext_id) { int length = LENGTH_UUID + 3; // UUID + (PUSH + id + length of dict) for (Pair pair : pairs) { if (pair.first == null || pair.second == null) @@ -1960,6 +1962,8 @@ public class PebbleProtocol extends GBDeviceProtocol { } } + idLookup[last_id] = ext_id; + return buf.array(); } @@ -1999,7 +2003,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } } - return encodeApplicationMessagePush(ENDPOINT_APPLICATIONMESSAGE, uuid, pairs); + return encodeApplicationMessagePush(ENDPOINT_APPLICATIONMESSAGE, uuid, pairs, null); } private byte reverseBits(byte in) { @@ -2597,12 +2601,24 @@ public class PebbleProtocol extends GBDeviceProtocol { } break; case APPLICATIONMESSAGE_ACK: - LOG.info("got APPLICATIONMESSAGE/LAUNCHER (EP " + endpoint + ") ACK"); - devEvts = new GBDeviceEvent[]{null}; - break; case APPLICATIONMESSAGE_NACK: - LOG.info("got APPLICATIONMESSAGE/LAUNCHER (EP " + endpoint + ") NACK"); - devEvts = new GBDeviceEvent[]{null}; + if (pebbleCmd == APPLICATIONMESSAGE_ACK) { + LOG.info("got APPLICATIONMESSAGE/LAUNCHER (EP " + endpoint + ") ACK"); + } else { + LOG.info("got APPLICATIONMESSAGE/LAUNCHER (EP " + endpoint + ") NACK"); + } + GBDeviceEventAppMessage evtAppMessage = null; + if (idLookup[last_id] != null) { + evtAppMessage = new GBDeviceEventAppMessage(); + if (pebbleCmd == APPLICATIONMESSAGE_ACK) { + evtAppMessage.type = GBDeviceEventAppMessage.TYPE_ACK; + } else { + evtAppMessage.type = GBDeviceEventAppMessage.TYPE_NACK; + } + evtAppMessage.id = idLookup[last_id]; + evtAppMessage.appUUID = uuid; + } + devEvts = new GBDeviceEvent[]{evtAppMessage}; break; case APPLICATIONMESSAGE_REQUEST: LOG.info("got APPLICATIONMESSAGE/LAUNCHER (EP " + endpoint + ") REQUEST"); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java index d9bd9f2d0..8f0cb3495 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java @@ -71,7 +71,7 @@ public class PebbleSupport extends AbstractSerialDeviceSupport { } @Override - public void onAppConfiguration(UUID uuid, String config) { + public void onAppConfiguration(UUID uuid, String config, Integer id) { try { ArrayList> pairs = new ArrayList<>(); @@ -92,7 +92,7 @@ public class PebbleSupport extends AbstractSerialDeviceSupport { } pairs.add(new Pair<>(Integer.parseInt(keyStr), object)); } - getDeviceIOThread().write(((PebbleProtocol) getDeviceProtocol()).encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, uuid, pairs)); + getDeviceIOThread().write(((PebbleProtocol) getDeviceProtocol()).encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, uuid, pairs, id)); } catch (JSONException e) { e.printStackTrace(); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vibratissimo/VibratissimoSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vibratissimo/VibratissimoSupport.java index 30d8ae362..3629fd19e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vibratissimo/VibratissimoSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vibratissimo/VibratissimoSupport.java @@ -196,7 +196,7 @@ public class VibratissimoSupport extends AbstractBTLEDeviceSupport { } @Override - public void onAppConfiguration(UUID appUuid, String config) { + public void onAppConfiguration(UUID appUuid, String config, Integer id) { } 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 2c35bd70e..fb2e86dae 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -128,6 +128,11 @@ public class WebViewSingleton { return; } + // TODO: handle ACK and NACK types with ids + if (message.type != GBDeviceEventAppMessage.TYPE_APPMESSAGE) { + return; + } + final String appMessage = parseIncomingAppMessage(message.message, message.appUUID); LOG.debug("to WEBVIEW: " + appMessage); new Handler(webViewSingleton.mainLooper).post(new Runnable() { @@ -481,7 +486,7 @@ public class WebViewSingleton { } LOG.info("WEBVIEW message to pebble: " + out.toString()); - GBApplication.deviceService().onAppConfiguration(this.mUuid, out.toString()); + GBApplication.deviceService().onAppConfiguration(this.mUuid, out.toString(), null); // TODO: insert local id for transaction } catch (JSONException e) { LOG.warn(e.getMessage()); diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java index f3239fb70..93414fae8 100644 --- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java +++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java @@ -105,7 +105,7 @@ class TestDeviceSupport extends AbstractDeviceSupport { } @Override - public void onAppConfiguration(UUID appUuid, String config) { + public void onAppConfiguration(UUID appUuid, String config, Integer id) { } From aa28625d9fb1b359e93d41c2a2001706aced88ff Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Wed, 2 Aug 2017 22:08:29 +0200 Subject: [PATCH 31/48] Pebble: JS basic implementation of transaction acknowledgment JAVA side: - first trivial handling of ACK/NACK message, - fake the location age until an update mechanism is implemented, - make the appmessage parsing more robust, - comment the forecast mimicked reply as it was wrong and confusing for some watchfaces JS side: first trivial handling of ACK/NACK message --- .../app_config/js/gadgetbridge_boilerplate.js | 30 +++++++-- .../gadgetbridge/util/WebViewSingleton.java | 66 +++++++++++++------ 2 files changed, 68 insertions(+), 28 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 e968422bf..2db5d90ff 100644 --- a/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js +++ b/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js @@ -159,17 +159,33 @@ function gbPebble() { if (document.getElementById("step2").style.display == 'block') { //intercept the values document.getElementById("jsondata").innerHTML=self.configurationValues; } else { //pass them silently - GBjs.sendAppMessage(JSON.stringify(dict)); - } - if (callbackAck != undefined) { - callbackAck(); + var needsTransaction = false; + if (callbackAck != undefined || callbackNack != undefined) { + needsTransaction = true; + } + var transactionId = GBjs.sendAppMessage(JSON.stringify(dict), needsTransaction); + if (needsTransaction) { + if (callbackAck != undefined) { + this.addEventListener("ACK"+transactionId, function(e) { +// console.log("ACK FOR " + JSON.stringify(e)); + callbackAck(e); + self.removeEventListener("ACK"+transactionId); + self.removeEventListener("NACK"+transactionId); + }); + } + if (callbackNack != undefined) { + this.addEventListener("NACK"+transactionId, function(e) { +// console.log("NACK FOR " + JSON.stringify(e)); + callbackNack(e); + self.removeEventListener("ACK"+transactionId); + self.removeEventListener("NACK"+transactionId); + }); + } + } } } catch (e) { GBjs.gbLog("sendAppMessage failed"); - if (callbackNack != undefined) { - callbackNack(); - } } } 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 fb2e86dae..06989162c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -123,22 +123,31 @@ public class WebViewSingleton { public static void appMessage(GBDeviceEventAppMessage message) { + final String jsEvent; if (webViewSingleton.instance == null) { LOG.warn("WEBVIEW is not initialized, cannot send appMessages to it"); return; } + if (!message.appUUID.equals(currentRunningUUID)) { + LOG.info("WEBVIEW ignoring message for app that is not currently running: " + message.appUUID + " message: " + message.message + " type: " + message.type); +// return; //TODO: ignoring would be the right thing to do here, but sometimes appUUID is apparently wrong + } + // TODO: handle ACK and NACK types with ids if (message.type != GBDeviceEventAppMessage.TYPE_APPMESSAGE) { - return; + jsEvent = (GBDeviceEventAppMessage.TYPE_NACK == GBDeviceEventAppMessage.TYPE_APPMESSAGE) ? "NACK" + message.id : "ACK" + message.id; + LOG.debug("WEBVIEW received ACK/NACK:" + message.message + " for uuid: " + message.appUUID + " ID: " + message.id); + } else { + jsEvent = "appmessage"; } final String appMessage = parseIncomingAppMessage(message.message, message.appUUID); - LOG.debug("to WEBVIEW: " + appMessage); + LOG.debug("to WEBVIEW: event: " + jsEvent + " message: " + appMessage); new Handler(webViewSingleton.mainLooper).post(new Runnable() { @Override public void run() { - webViewSingleton.instance.evaluateJavascript("Pebble.evaluate('appmessage',[" + appMessage + "]);", new ValueCallback() { + webViewSingleton.instance.evaluateJavascript("Pebble.evaluate('" + jsEvent + "',[" + appMessage + "]);", new ValueCallback() { @Override public void onReceiveValue(String s) { //TODO: the message should be acked here instead of in PebbleIoThread @@ -192,6 +201,7 @@ public class WebViewSingleton { Location lastKnownLocation = locationManager.getLastKnownLocation(provider); if (lastKnownLocation != null) { this.timestamp = lastKnownLocation.getTime(); + this.timestamp = System.currentTimeMillis() - 1000; //TODO: request updating the location and don't fake its age this.latitude = (float) lastKnownLocation.getLatitude(); this.longitude = (float) lastKnownLocation.getLongitude(); @@ -260,20 +270,20 @@ public class WebViewSingleton { resp.put("cod", 200); resp.put("coord", coordObject(currentPosition)); resp.put("sys", sysObject(currentPosition)); - } else if ("/data/2.5/forecast".equals(type) && Weather.getInstance().getWeather2().reconstructedForecast != null) { //this is wrong, as we only have daily data. Unfortunately it looks like daily forecasts cannot be reconstructed - resp = new JSONObject(Weather.getInstance().getWeather2().reconstructedForecast.toString()); - - JSONObject city = resp.getJSONObject("city"); - city.put("coord", coordObject(currentPosition)); - - JSONArray list = resp.getJSONArray("list"); - for (int i = 0, size = list.length(); i < size; i++) { - JSONObject item = list.getJSONObject(i); - JSONObject main = item.getJSONObject("main"); - convertTemps(main, units); //caller might want different units - } - - resp.put("cod", 200); +// } else if ("/data/2.5/forecast".equals(type) && Weather.getInstance().getWeather2().reconstructedForecast != null) { //this is wrong, as we only have daily data. Unfortunately it looks like daily forecasts cannot be reconstructed +// resp = new JSONObject(Weather.getInstance().getWeather2().reconstructedForecast.toString()); +// +// JSONObject city = resp.getJSONObject("city"); +// city.put("coord", coordObject(currentPosition)); +// +// JSONArray list = resp.getJSONArray("list"); +// for (int i = 0, size = list.length(); i < size; i++) { +// JSONObject item = list.getJSONObject(i); +// JSONObject main = item.getJSONObject("main"); +// convertTemps(main, units); //caller might want different units +// } +// +// resp.put("cod", 200); } else { LOG.warn("WEBVIEW - cannot mimick request of type " + type + " (unsupported or lack of data)"); return null; @@ -393,6 +403,10 @@ public class WebViewSingleton { JSONObject knownKeys = getAppConfigurationKeys(uuid); SparseArray appKeysMap = new SparseArray<>(); + if (knownKeys == null) { + return "{}"; + } + String inKey, outKey; //knownKeys contains "name"->"index", we need to reverse that for (Iterator key = knownKeys.keys(); key.hasNext(); ) { @@ -423,7 +437,7 @@ public class WebViewSingleton { } jsAppMessage.put("payload", outgoing); - } catch (JSONException e) { + } catch (Exception e) { LOG.warn(e.getMessage()); } return jsAppMessage.toString(); @@ -433,11 +447,13 @@ public class WebViewSingleton { UUID mUuid; GBDevice device; + Integer lastTransaction; private JSInterface(GBDevice device, UUID mUuid) { LOG.debug("Creating JS interface for UUID: " + mUuid.toString()); this.device = device; this.mUuid = mUuid; + this.lastTransaction = 0; } @@ -451,8 +467,9 @@ public class WebViewSingleton { } @JavascriptInterface - public void sendAppMessage(String msg) { - LOG.debug("from WEBVIEW: " + msg); + public String sendAppMessage(String msg, String needsTransactionMsg) { + boolean needsTransaction = "true".equals(needsTransactionMsg); + LOG.debug("from WEBVIEW: " + msg + " needs a transaction: " + needsTransaction); JSONObject knownKeys = getAppConfigurationKeys(this.mUuid); try { @@ -486,11 +503,18 @@ public class WebViewSingleton { } LOG.info("WEBVIEW message to pebble: " + out.toString()); - GBApplication.deviceService().onAppConfiguration(this.mUuid, out.toString(), null); // TODO: insert local id for transaction + if (needsTransaction) { + this.lastTransaction++; + GBApplication.deviceService().onAppConfiguration(this.mUuid, out.toString(), this.lastTransaction); + return this.lastTransaction.toString(); + } else { + GBApplication.deviceService().onAppConfiguration(this.mUuid, out.toString(), null); + } } catch (JSONException e) { LOG.warn(e.getMessage()); } + return null; } @JavascriptInterface From 9cca16cb0041cf76ddae527ce682f7d0ee816e91 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Wed, 2 Aug 2017 22:18:11 +0200 Subject: [PATCH 32/48] Pebble: JS fix sending configuration data (regression introduced with last commit) --- app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2db5d90ff..dd95239ff 100644 --- a/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js +++ b/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js @@ -117,7 +117,7 @@ function gbPebble() { } this.actuallySendData = function() { - GBjs.sendAppMessage(self.configurationValues); + GBjs.sendAppMessage(self.configurationValues, false ); showStep("step1"); GBActivity.closeActivity(); } From 6916beabedeaa971fedc1d6b62e30ae34541f3ae Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Wed, 2 Aug 2017 23:09:43 +0200 Subject: [PATCH 33/48] Pebble: fix wrong uuid being passed from protocol to js --- .../gadgetbridge/service/devices/pebble/PebbleProtocol.java | 4 ++-- .../freeyourgadget/gadgetbridge/util/WebViewSingleton.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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 7c3645681..f67a11f1c 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 @@ -2608,7 +2608,7 @@ public class PebbleProtocol extends GBDeviceProtocol { LOG.info("got APPLICATIONMESSAGE/LAUNCHER (EP " + endpoint + ") NACK"); } GBDeviceEventAppMessage evtAppMessage = null; - if (idLookup[last_id] != null) { + if (endpoint == ENDPOINT_APPLICATIONMESSAGE && idLookup[last_id] != null) { evtAppMessage = new GBDeviceEventAppMessage(); if (pebbleCmd == APPLICATIONMESSAGE_ACK) { evtAppMessage.type = GBDeviceEventAppMessage.TYPE_ACK; @@ -2616,7 +2616,7 @@ public class PebbleProtocol extends GBDeviceProtocol { evtAppMessage.type = GBDeviceEventAppMessage.TYPE_NACK; } evtAppMessage.id = idLookup[last_id]; - evtAppMessage.appUUID = uuid; + evtAppMessage.appUUID = currentRunningApp; } devEvts = new GBDeviceEvent[]{evtAppMessage}; break; 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 06989162c..6ed0a9919 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -131,7 +131,7 @@ public class WebViewSingleton { if (!message.appUUID.equals(currentRunningUUID)) { LOG.info("WEBVIEW ignoring message for app that is not currently running: " + message.appUUID + " message: " + message.message + " type: " + message.type); -// return; //TODO: ignoring would be the right thing to do here, but sometimes appUUID is apparently wrong + return; } // TODO: handle ACK and NACK types with ids From 7475d170a7a1f7c1106c6395f52867bc77263aa2 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Sat, 5 Aug 2017 16:04:48 +0200 Subject: [PATCH 34/48] Pebble: add support for the (unreleased) internet helper addon This will use the internet helper application if it's available on the android device or fall back to existing methods if it doesn't. --- .../gadgetbridge/util/WebViewSingleton.java | 78 ++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) 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 6ed0a9919..23ba136b0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -3,17 +3,24 @@ package nodomain.freeyourgadget.gadgetbridge.util; import android.Manifest; import android.annotation.TargetApi; import android.app.Activity; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.MutableContextWrapper; +import android.content.ServiceConnection; 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.Bundle; import android.os.Handler; +import android.os.IBinder; import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.util.SparseArray; @@ -52,6 +59,7 @@ import java.util.Iterator; import java.util.Map; import java.util.Scanner; import java.util.UUID; +import java.util.concurrent.CountDownLatch; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage; @@ -67,6 +75,11 @@ public class WebViewSingleton { private Looper mainLooper; private static WebViewSingleton webViewSingleton = new WebViewSingleton(); private static UUID currentRunningUUID; + private static Messenger internetHelper = null; + private static boolean internetHelperBound; + private static CountDownLatch latch; + private static WebResourceResponse internetResponse; + final static Messenger internetHelperListener = new Messenger(new IncomingHandler()); private WebViewSingleton() { } @@ -87,10 +100,48 @@ public class WebViewSingleton { webSettings.setDomStorageEnabled(true); //needed for localstorage webSettings.setDatabaseEnabled(true); + Intent intent = new Intent(); + intent.setComponent(new ComponentName("nodomain.freeyourgadget.internethelper", "nodomain.freeyourgadget.internethelper.MyService")); + context.getApplicationContext().bindService(intent, internetHelperConnection, Context.BIND_AUTO_CREATE); } return webViewSingleton.instance; } + //Internet helper outgoing connection + private static ServiceConnection internetHelperConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + internetHelperBound = true; + internetHelper = new Messenger(service); + } + + public void onServiceDisconnected(ComponentName className) { + internetHelper = null; + internetHelperBound = false; + } + }; + + //Internet helper inbound (responses) handler + static class IncomingHandler extends Handler { + @Override + public void handleMessage(Message msg) { + Bundle data = msg.getData(); + LOG.debug("WEBVIEW: internet helper returned: " + data.getString("response")); + Map headers = new HashMap<>(); + headers.put("Access-Control-Allow-Origin", "*"); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + internetResponse = new WebResourceResponse(data.getString("content-type"), data.getString("content-encoding"), 200, "OK", + headers, + new ByteArrayInputStream(data.getString("response").getBytes()) + ); + } else { + internetResponse = new WebResourceResponse(data.getString("content-type"), data.getString("content-encoding"), new ByteArrayInputStream(data.getString("response").getBytes())); + } + + latch.countDown(); + } + } + public static void updateActivityContext(Activity context) { if (context != null) { webViewSingleton.contextWrapper.setBaseContext(context); @@ -159,6 +210,10 @@ public class WebViewSingleton { } public static void disposeWebView() { + if (internetHelperBound) { + LOG.debug("WEBVIEW: will unbind the internet helper"); + webViewSingleton.contextWrapper.getApplicationContext().unbindService(internetHelperConnection); + } new Handler(webViewSingleton.mainLooper).post(new Runnable() { @Override public void run() { @@ -353,8 +408,27 @@ public class WebViewSingleton { private WebResourceResponse mimicReply(Uri requestedUri) { if (requestedUri.getHost() != null && requestedUri.getHost().contains("openweathermap.org")) { - LOG.debug("WEBVIEW request to openweathermap.org detected of type: " + requestedUri.getPath() + " params: " + requestedUri.getQuery()); - return mimicOpenWeatherMapResponse(requestedUri.getPath(), requestedUri.getQueryParameter("units")); + if (internetHelperBound) { + LOG.debug("WEBVIEW forwarding request to the internet helper"); + Bundle bundle = new Bundle(); + bundle.putString("URL", requestedUri.toString()); + Message webRequest = Message.obtain(); + webRequest.replyTo = internetHelperListener; + webRequest.setData(bundle); + try { + latch = new CountDownLatch(1); //the messenger should run on a single thread, hence we don't need to be worried about concurrency. This approach however is certainly not ideal. + internetHelper.send(webRequest); + latch.await(); + return internetResponse; + + } catch (RemoteException | InterruptedException e) { + e.printStackTrace(); + } + + } else { + LOG.debug("WEBVIEW request to openweathermap.org detected of type: " + requestedUri.getPath() + " params: " + requestedUri.getQuery()); + return mimicOpenWeatherMapResponse(requestedUri.getPath(), requestedUri.getQueryParameter("units")); + } } else { LOG.debug("WEBVIEW request:" + requestedUri.toString() + " not intercepted"); } From 868c0543d59554c6083e1f168b9ff5d4edc548ff Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Mon, 7 Aug 2017 18:35:09 +0200 Subject: [PATCH 35/48] Pebble: JS proper removal of callbacks (transaction handling) --- .../app_config/js/gadgetbridge_boilerplate.js | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 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 dd95239ff..7f07ec211 100644 --- a/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js +++ b/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js @@ -79,6 +79,21 @@ function gbPebble() { this.configurationURL = null; this.configurationValues = null; var self = this; + + appMessageCallbackACK = {}; + appMessageCallbackNACK = {}; + + function appMessageCallbackProcessed(transactionId) { + if (appMessageCallbackACK[transactionId]) { + self.removeEventListener("ACK"+transactionId, self.appMessageCallbackACK[transactionId]); + appMessageCallbackACK[transactionId] = undefined; + } + if (appMessageCallbackNACK[transactionId]) { + self.removeEventListener("NACK"+transactionId, self.appMessageCallbackNACK[transactionId]); + appMessageCallbackNACK[transactionId] = undefined; + } + + } self.events = {}; //events processing: see http://stackoverflow.com/questions/10978311/implementing-events-in-my-own-object self.addEventListener = function(name, handler) { @@ -166,20 +181,23 @@ function gbPebble() { var transactionId = GBjs.sendAppMessage(JSON.stringify(dict), needsTransaction); if (needsTransaction) { if (callbackAck != undefined) { - this.addEventListener("ACK"+transactionId, function(e) { + self.appMessageCallbackACK[transactionId] = function(e) { // console.log("ACK FOR " + JSON.stringify(e)); callbackAck(e); - self.removeEventListener("ACK"+transactionId); - self.removeEventListener("NACK"+transactionId); - }); + self.appMessageCallbackProcessed(transactionId); + }; + + this.addEventListener("ACK"+transactionId, self.appMessageCallbackACK[transactionId]); + } if (callbackNack != undefined) { - this.addEventListener("NACK"+transactionId, function(e) { + self.appMessageCallbackNACK[transactionId] = function(e) { // console.log("NACK FOR " + JSON.stringify(e)); callbackNack(e); - self.removeEventListener("ACK"+transactionId); - self.removeEventListener("NACK"+transactionId); - }); + self.appMessageCallbackProcessed(transactionId); + }; + + this.addEventListener("NACK"+transactionId, self.appMessageCallbackNACK[transactionId]); } } } From 65835db5cccd718edc2f527fe007008faa4e14f8 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Thu, 10 Aug 2017 00:33:54 +0200 Subject: [PATCH 36/48] Pebble: more debug output for webview singleton, whitelist tagesschau.de for tests --- .../freeyourgadget/gadgetbridge/util/WebViewSingleton.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 23ba136b0..ba3c72ac4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -79,7 +79,7 @@ public class WebViewSingleton { private static boolean internetHelperBound; private static CountDownLatch latch; private static WebResourceResponse internetResponse; - final static Messenger internetHelperListener = new Messenger(new IncomingHandler()); + private final static Messenger internetHelperListener = new Messenger(new IncomingHandler()); private WebViewSingleton() { } @@ -110,11 +110,13 @@ public class WebViewSingleton { //Internet helper outgoing connection private static ServiceConnection internetHelperConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { + LOG.info("internet helper service bound"); internetHelperBound = true; internetHelper = new Messenger(service); } public void onServiceDisconnected(ComponentName className) { + LOG.info("internet helper service unbound"); internetHelper = null; internetHelperBound = false; } @@ -407,7 +409,7 @@ public class WebViewSingleton { } private WebResourceResponse mimicReply(Uri requestedUri) { - if (requestedUri.getHost() != null && requestedUri.getHost().contains("openweathermap.org")) { + if (requestedUri.getHost() != null && (requestedUri.getHost().contains("openweathermap.org") || requestedUri.getHost().contains("tagesschau.de") )) { if (internetHelperBound) { LOG.debug("WEBVIEW forwarding request to the internet helper"); Bundle bundle = new Bundle(); From f1d252c7ee3a4e4a25da06501b034d14087cdf00 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Tue, 22 Aug 2017 20:14:41 +0200 Subject: [PATCH 37/48] Disable appmessage handler for healthify and trekvolle --- .../gadgetbridge/service/devices/pebble/PebbleProtocol.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 f67a11f1c..ff0eb40d5 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 @@ -406,8 +406,8 @@ public class PebbleProtocol extends GBDeviceProtocol { // 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_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)); From d669d3b32559f8d33be172f128c2de1238393b63 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Sun, 3 Sep 2017 17:09:19 +0200 Subject: [PATCH 38/48] Make NoF1 compatible with background js branch --- .../gadgetbridge/service/devices/no1f1/No1F1Support.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/no1f1/No1F1Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/no1f1/No1F1Support.java index 4d4e8f293..f8f3d9a80 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/no1f1/No1F1Support.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/no1f1/No1F1Support.java @@ -190,7 +190,7 @@ public class No1F1Support extends AbstractBTLEDeviceSupport { } @Override - public void onAppConfiguration(UUID appUuid, String config) { + public void onAppConfiguration(UUID appUuid, String config, Integer id) { } From b8a137226f0f3a827b664e3c5183cb39c6555447 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Sun, 3 Sep 2017 17:10:04 +0200 Subject: [PATCH 39/48] Webview: Fix sending configuration from watchfaces when background message exchange is going on --- app/src/main/assets/app_config/configure.html | 2 +- .../main/assets/app_config/js/gadgetbridge_boilerplate.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/assets/app_config/configure.html b/app/src/main/assets/app_config/configure.html index cdc7669f5..a27544b80 100644 --- a/app/src/main/assets/app_config/configure.html +++ b/app/src/main/assets/app_config/configure.html @@ -89,7 +89,7 @@

Incoming configuration data:

-

App Presets:

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 13c7b47a2..f29e8164b 100644 --- a/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js +++ b/app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js @@ -138,8 +138,8 @@ function gbPebble() { window.open(self.configurationURL.toString(), "config"); } - this.actuallySendData = function() { - GBjs.sendAppMessage(self.configurationValues, false ); + this.sendConfiguration = function() { + GBjs.sendAppMessage(document.getElementById("jsondata").innerHTML, false ); showStep("step1"); GBActivity.closeActivity(); } @@ -189,7 +189,7 @@ function gbPebble() { this.sendAppMessage = function (dict, callbackAck, callbackNack){ try { self.configurationValues = JSON.stringify(dict); - if (document.getElementById("step2").style.display == 'block') { //intercept the values + if (document.getElementById("step2").style.display == 'block' && document.getElementById("jsondata").innerHTML == "") { //intercept the values document.getElementById("jsondata").innerHTML=self.configurationValues; } else { //pass them silently var needsTransaction = false; From df2a313168ace5387286322704141ba2c966a25f Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Sun, 3 Sep 2017 17:55:00 +0200 Subject: [PATCH 40/48] Webview: make the webview more robust to disconnections --- .../gadgetbridge/util/WebViewSingleton.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) 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 ba3c72ac4..66823b191 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -100,9 +100,6 @@ public class WebViewSingleton { webSettings.setDomStorageEnabled(true); //needed for localstorage webSettings.setDatabaseEnabled(true); - Intent intent = new Intent(); - intent.setComponent(new ComponentName("nodomain.freeyourgadget.internethelper", "nodomain.freeyourgadget.internethelper.MyService")); - context.getApplicationContext().bindService(intent, internetHelperConnection, Context.BIND_AUTO_CREATE); } return webViewSingleton.instance; } @@ -165,11 +162,17 @@ public class WebViewSingleton { new Handler(webViewSingleton.mainLooper).post(new Runnable() { @Override public void run() { + webViewSingleton.instance.onResume(); webViewSingleton.instance.removeJavascriptInterface("GBjs"); webViewSingleton.instance.addJavascriptInterface(jsInterface, "GBjs"); webViewSingleton.instance.loadUrl("file:///android_asset/app_config/configure.html?rand=" + Math.random() * 500); } }); + if (!internetHelperBound) { + Intent intent = new Intent(); + intent.setComponent(new ComponentName("nodomain.freeyourgadget.internethelper", "nodomain.freeyourgadget.internethelper.MyService")); + webViewSingleton.contextWrapper.getApplicationContext().bindService(intent, internetHelperConnection, Context.BIND_AUTO_CREATE); + } } } @@ -215,13 +218,16 @@ public class WebViewSingleton { if (internetHelperBound) { LOG.debug("WEBVIEW: will unbind the internet helper"); webViewSingleton.contextWrapper.getApplicationContext().unbindService(internetHelperConnection); + internetHelperBound = false; } + currentRunningUUID = null; new Handler(webViewSingleton.mainLooper).post(new Runnable() { @Override public void run() { if (webViewSingleton.instance != null) { - webViewSingleton.instance.setWebChromeClient(null); - webViewSingleton.instance.setWebViewClient(null); + webViewSingleton.instance.removeJavascriptInterface("GBjs"); +// webViewSingleton.instance.setWebChromeClient(null); +// webViewSingleton.instance.setWebViewClient(null); webViewSingleton.instance.clearHistory(); webViewSingleton.instance.clearCache(true); webViewSingleton.instance.loadUrl("about:blank"); From 365fcace06fa689455352074e1f7497a2aee96c6 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Tue, 5 Sep 2017 10:20:37 +0200 Subject: [PATCH 41/48] Fix merge --- .../gadgetbridge/service/devices/jyou/TeclastH30Support.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/TeclastH30Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/TeclastH30Support.java index 36406f6a0..8b1f09a84 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/TeclastH30Support.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/jyou/TeclastH30Support.java @@ -336,7 +336,7 @@ public class TeclastH30Support extends AbstractBTLEDeviceSupport { } @Override - public void onAppConfiguration(UUID appUuid, String config) { + public void onAppConfiguration(UUID appUuid, String config, Integer id) { } From e8ae47de7993b3f71c1a3ff33cfbe88b34e59af9 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Mon, 25 Sep 2017 17:12:35 +0200 Subject: [PATCH 42/48] Pebble: Make background JS support toggle-able - Add preference to enable background JS (default disabled) - Remove the dummy activity used to create the webview, use ExternalPebbleJSActivity instead - Add layout for legacy configuration, used if background JS is not enabled - Create the view upon connecting, not when launching the application - Remove the generic helpers used to find out if any device would need the background webview - Drastic refactoring of WebviewSingleton moving internal classes in a new package "webview" in service/devices/pebble --- app/src/main/AndroidManifest.xml | 4 - .../gadgetbridge/GBApplication.java | 12 - .../activities/BackgroundWebViewActivity.java | 15 - .../activities/ExternalPebbleJSActivity.java | 88 +++- .../AbstractAppManagerFragment.java | 1 + .../devices/AbstractDeviceCoordinator.java | 5 - .../devices/DeviceCoordinator.java | 8 - .../devices/pebble/PebbleCoordinator.java | 6 - .../devices/pebble/PebbleIoThread.java | 21 +- .../pebble/webview/CurrentPosition.java | 61 +++ .../pebble/webview/GBChromeClient.java | 29 ++ .../devices/pebble/webview/GBWebClient.java | 204 ++++++++ .../devices/pebble/webview/JSInterface.java | 233 +++++++++ .../gadgetbridge/util/DeviceHelper.java | 15 - .../gadgetbridge/util/WebViewSingleton.java | 479 +----------------- .../activity_legacy_external_pebble_js.xml | 5 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/preferences.xml | 5 + 18 files changed, 635 insertions(+), 558 deletions(-) delete mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/CurrentPosition.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBChromeClient.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java create mode 100644 app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/JSInterface.java create mode 100644 app/src/main/res/layout/activity_legacy_external_pebble_js.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1b170f94c..49ae11746 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -362,10 +362,6 @@ android:name=".activities.DiscoveryActivity" android:label="@string/title_activity_discovery" android:parentActivityName=".activities.ControlCenterv2" /> - diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java index 5e2105583..7ee81ed53 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java @@ -47,7 +47,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import nodomain.freeyourgadget.gadgetbridge.activities.BackgroundWebViewActivity; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.database.DBOpenHelper; @@ -58,7 +57,6 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.service.NotificationCollectorMonitorService; -import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -167,8 +165,6 @@ public class GBApplication extends Application { String language = prefs.getString("language", "default"); setLanguage(language); - createWebViewActivity(); - deviceService = createDeviceService(); loadAppsBlackList(); loadCalendarsBlackList(); @@ -180,14 +176,6 @@ public class GBApplication extends Application { } } - private void createWebViewActivity() { - if (DeviceHelper.getInstance().needsBackgroundWebView(this)) { - Intent intent = new Intent(getContext(), BackgroundWebViewActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - } - } - @Override public void onTrimMemory(int level) { super.onTrimMemory(level); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java deleted file mode 100644 index d903fe0d9..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/BackgroundWebViewActivity.java +++ /dev/null @@ -1,15 +0,0 @@ -package nodomain.freeyourgadget.gadgetbridge.activities; - -import android.app.Activity; -import android.os.Bundle; - -import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton; - -public class BackgroundWebViewActivity extends Activity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - WebViewSingleton.getInstance(this); - finish(); - } -} 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 ca4790eb0..5970eafc1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java @@ -24,7 +24,9 @@ import android.os.Bundle; import android.support.v4.app.NavUtils; import android.view.MenuItem; import android.view.View; +import android.view.ViewGroup; import android.webkit.JavascriptInterface; +import android.webkit.WebSettings; import android.webkit.WebView; import android.widget.FrameLayout; import android.widget.Toast; @@ -34,9 +36,13 @@ import org.slf4j.LoggerFactory; import java.util.UUID; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; +import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.GBChromeClient; +import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.GBWebClient; +import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.JSInterface; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton; @@ -46,41 +52,77 @@ public class ExternalPebbleJSActivity extends AbstractGBActivity { private Uri confUri; private WebView myWebView; + public static final String START_BG_WEBVIEW = "start_webview"; + public static final String SHOW_CONFIG = "configure"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - + GBDevice currentDevice; + UUID currentUUID; Bundle extras = getIntent().getExtras(); if (extras != null) { - WebViewSingleton.runJavascriptInterface((GBDevice) extras.getParcelable(GBDevice.EXTRA_DEVICE), (UUID) extras.getSerializable(DeviceService.EXTRA_APP_UUID)); + currentDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE); + currentUUID = (UUID) extras.getSerializable(DeviceService.EXTRA_APP_UUID); + + if (GBApplication.getPrefs().getBoolean("pebble_enable_background_javascript", false)) { + if (extras.getBoolean(SHOW_CONFIG, false)) { + WebViewSingleton.runJavascriptInterface(currentDevice, currentUUID); + } else if (extras.getBoolean(START_BG_WEBVIEW, false)) { + WebViewSingleton.getInstance(this); + finish(); + } + } } else { throw new IllegalArgumentException("Must provide a device when invoking this activity"); } - setContentView(R.layout.activity_external_pebble_js); - WebViewSingleton.updateActivityContext(this); - myWebView = WebViewSingleton.getWebView(); - myWebView.setWillNotDraw(false); - myWebView.removeJavascriptInterface("GBActivity"); - myWebView.addJavascriptInterface(new ActivityJSInterface(ExternalPebbleJSActivity.this), "GBActivity"); - FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder); - fl.addView(myWebView); - - myWebView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - v.setLayerType(View.LAYER_TYPE_HARDWARE, null); + if (GBApplication.getPrefs().getBoolean("pebble_enable_background_javascript", false)) { + setContentView(R.layout.activity_external_pebble_js); + myWebView = WebViewSingleton.getWebView(this); + if (myWebView.getParent() != null) { + ((ViewGroup) myWebView.getParent()).removeView(myWebView); } + myWebView.setWillNotDraw(false); + myWebView.removeJavascriptInterface("GBActivity"); + myWebView.addJavascriptInterface(new ActivityJSInterface(ExternalPebbleJSActivity.this), "GBActivity"); + FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder); + fl.addView(myWebView); - @Override - public void onViewDetachedFromWindow(View v) { - v.removeOnAttachStateChangeListener(this); - FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder); - fl.removeAllViews(); - } - }); + myWebView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + v.setLayerType(View.LAYER_TYPE_HARDWARE, null); + } + + @Override + public void onViewDetachedFromWindow(View v) { + v.removeOnAttachStateChangeListener(this); + FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder); + fl.removeAllViews(); + } + }); + } else { + setContentView(R.layout.activity_legacy_external_pebble_js); + myWebView = (WebView) findViewById(R.id.configureWebview); + myWebView.clearCache(true); + myWebView.setWebViewClient(new GBWebClient()); + myWebView.setWebChromeClient(new GBChromeClient()); + WebSettings webSettings = myWebView.getSettings(); + webSettings.setJavaScriptEnabled(true); + //needed to access the DOM + webSettings.setDomStorageEnabled(true); + //needed for localstorage + webSettings.setDatabaseEnabled(true); + + JSInterface gbJSInterface = new JSInterface(currentDevice, currentUUID); + myWebView.addJavascriptInterface(gbJSInterface, "GBjs"); + myWebView.addJavascriptInterface(new ActivityJSInterface(ExternalPebbleJSActivity.this), "GBActivity"); + + myWebView.loadUrl("file:///android_asset/app_config/configure.html"); + + } } @Override @@ -119,7 +161,7 @@ public class ExternalPebbleJSActivity extends AbstractGBActivity { Context mContext; - public ActivityJSInterface(Context c) { + ActivityJSInterface(Context c) { mContext = c; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java index 90008f0cc..1ac4b1530 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/appmanager/AbstractAppManagerFragment.java @@ -424,6 +424,7 @@ public abstract class AbstractAppManagerFragment extends Fragment { Intent startIntent = new Intent(getContext().getApplicationContext(), ExternalPebbleJSActivity.class); startIntent.putExtra(DeviceService.EXTRA_APP_UUID, selectedApp.getUUID()); startIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice); + startIntent.putExtra(ExternalPebbleJSActivity.SHOW_CONFIG, true); startActivity(startIntent); return true; case R.id.appmanager_app_openinstore: diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java index 1bdc60849..7c4e04124 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractDeviceCoordinator.java @@ -120,11 +120,6 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator { return false; } - @Override - public boolean needsBackgroundWebView(GBDevice device) { - return false; - } - @Override public int getBondingStyle(GBDevice device) { return BONDING_STYLE_ASK; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java index b4b74d7e4..821d734f9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/DeviceCoordinator.java @@ -220,14 +220,6 @@ public interface DeviceCoordinator { */ Class getAppsManagementActivity(); - /** - * Returns true if the given device needs a background webview for - * executing javascript or configuration, for example. - * - * @param device - */ - boolean needsBackgroundWebView(GBDevice device); - /** * Returns how/if the given device should be bonded before connecting to it. * @param device diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java index 04862d915..9cf50d1e0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleCoordinator.java @@ -25,7 +25,6 @@ import android.support.annotation.NonNull; import de.greenrobot.dao.query.QueryBuilder; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBException; -import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity; import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; @@ -157,11 +156,6 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator { return true; } - @Override - public boolean needsBackgroundWebView(GBDevice device) { - return true; - } - @Override public boolean supportsRealtimeData() { return false; 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 b7cf06083..9059dec85 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 @@ -43,6 +43,7 @@ import java.nio.ByteOrder; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.ExternalPebbleJSActivity; import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AbstractAppManagerFragment; import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActivity; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; @@ -161,6 +162,14 @@ class PebbleIoThread extends GBDeviceIoThread { mBtSocket.connect(); mInStream = mBtSocket.getInputStream(); mOutStream = mBtSocket.getOutputStream(); + + if (prefs.getBoolean("pebble_enable_background_javascript", false)) { + Intent startIntent = new Intent(getContext(), ExternalPebbleJSActivity.class); + startIntent.putExtra(ExternalPebbleJSActivity.START_BG_WEBVIEW, true); + getContext().startActivity(startIntent); + } else { + LOG.debug("Not enabling background Webview, is disabled in preferences."); + } } } } catch (IOException e) { @@ -380,7 +389,9 @@ class PebbleIoThread extends GBDeviceIoThread { gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT); } - WebViewSingleton.disposeWebView(); + if (prefs.getBoolean("pebble_enable_background_javascript", false)) { + WebViewSingleton.disposeWebView(); + } gbDevice.sendDeviceUpdateIntent(getContext()); } @@ -506,7 +517,9 @@ class PebbleIoThread extends GBDeviceIoThread { break; case START: LOG.info("got GBDeviceEventAppManagement START event for uuid: " + appMgmt.uuid); - WebViewSingleton.runJavascriptInterface(gbDevice, appMgmt.uuid); + if (prefs.getBoolean("pebble_enable_background_javascript", false)) { + WebViewSingleton.runJavascriptInterface(gbDevice, appMgmt.uuid); + } break; default: break; @@ -518,7 +531,9 @@ class PebbleIoThread extends GBDeviceIoThread { setInstallSlot(appInfoEvent.freeSlot); return false; } else if (deviceEvent instanceof GBDeviceEventAppMessage) { - sendAppMessageJS((GBDeviceEventAppMessage) deviceEvent); + if (GBApplication.getPrefs().getBoolean("pebble_enable_background_javascript", false)) { + sendAppMessageJS((GBDeviceEventAppMessage) deviceEvent); + } if (mEnablePebblekit) { LOG.info("Got AppMessage event"); if (mPebbleKitSupport != null && ((GBDeviceEventAppMessage) deviceEvent).type == GBDeviceEventAppMessage.TYPE_APPMESSAGE) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/CurrentPosition.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/CurrentPosition.java new file mode 100644 index 000000000..56ace224f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/CurrentPosition.java @@ -0,0 +1,61 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.location.Criteria; +import android.location.Location; +import android.location.LocationManager; +import android.support.v4.app.ActivityCompat; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; + +class CurrentPosition { + + private static final Logger LOG = LoggerFactory.getLogger(CurrentPosition.class); + + long timestamp; + double altitude; + float latitude, longitude, accuracy, speed; + + float getLatitude() { + return latitude; + } + + float getLongitude() { + return longitude; + } + + CurrentPosition() { + Prefs prefs = GBApplication.getPrefs(); + this.latitude = prefs.getFloat("location_latitude", 0); + this.longitude = prefs.getFloat("location_longitude", 0); + LOG.info("got longitude/latitude from preferences: " + latitude + "/" + longitude); + + this.timestamp = System.currentTimeMillis() - 86400000; //let accessor know this value is really old + + if (ActivityCompat.checkSelfPermission(GBApplication.getContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED && + prefs.getBoolean("use_updated_location_if_available", false)) { + LocationManager locationManager = (LocationManager) GBApplication.getContext().getSystemService(Context.LOCATION_SERVICE); + Criteria criteria = new Criteria(); + String provider = locationManager.getBestProvider(criteria, false); + if (provider != null) { + Location lastKnownLocation = locationManager.getLastKnownLocation(provider); + if (lastKnownLocation != null) { + this.timestamp = lastKnownLocation.getTime(); + this.timestamp = System.currentTimeMillis() - 1000; //TODO: request updating the location and don't fake its age + + this.latitude = (float) lastKnownLocation.getLatitude(); + this.longitude = (float) lastKnownLocation.getLongitude(); + this.accuracy = lastKnownLocation.getAccuracy(); + this.altitude = (float) lastKnownLocation.getAltitude(); + this.speed = lastKnownLocation.getSpeed(); + } + } + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBChromeClient.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBChromeClient.java new file mode 100644 index 000000000..4868a255c --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBChromeClient.java @@ -0,0 +1,29 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview; + +import android.webkit.ConsoleMessage; +import android.webkit.WebChromeClient; +import android.widget.Toast; + +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +public class GBChromeClient extends WebChromeClient { + + @Override + public boolean onConsoleMessage(ConsoleMessage consoleMessage) { + if (ConsoleMessage.MessageLevel.ERROR.equals(consoleMessage.messageLevel())) { + GB.toast(formatConsoleMessage(consoleMessage), Toast.LENGTH_LONG, GB.ERROR); + //TODO: show error page + } + return super.onConsoleMessage(consoleMessage); + } + + private static String formatConsoleMessage(ConsoleMessage message) { + String sourceId = message.sourceId(); + if (sourceId == null || sourceId.length() == 0) { + sourceId = "unknown"; + } + return String.format("%s (at %s: %d)", message.message(), sourceId, message.lineNumber()); + } + + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java new file mode 100644 index 000000000..5962ef236 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java @@ -0,0 +1,204 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview; + +import android.annotation.TargetApi; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Message; +import android.os.RemoteException; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import net.e175.klaus.solarpositioning.DeltaT; +import net.e175.klaus.solarpositioning.SPA; + +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.model.Weather; + +import static nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton.internetHelper; +import static nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton.internetHelperBound; +import static nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton.internetHelperListener; +import static nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton.internetResponse; +import static nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton.latch; + +public class GBWebClient extends WebViewClient { + + private static final Logger LOG = LoggerFactory.getLogger(GBWebClient.class); + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { + Uri parsedUri = request.getUrl(); + LOG.debug("WEBVIEW shouldInterceptRequest URL: " + parsedUri.toString()); + WebResourceResponse mimickedReply = mimicReply(parsedUri); + if (mimickedReply != null) + return mimickedReply; + return super.shouldInterceptRequest(view, request); + } + + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, String url) { + LOG.debug("WEBVIEW shouldInterceptRequest URL (legacy): " + url); + Uri parsedUri = Uri.parse(url); + WebResourceResponse mimickedReply = mimicReply(parsedUri); + if (mimickedReply != null) + return mimickedReply; + return super.shouldInterceptRequest(view, url); + } + + private WebResourceResponse mimicReply(Uri requestedUri) { + if (requestedUri.getHost() != null && (requestedUri.getHost().contains("openweathermap.org") || requestedUri.getHost().contains("tagesschau.de"))) { + if (internetHelperBound) { + LOG.debug("WEBVIEW forwarding request to the internet helper"); + Bundle bundle = new Bundle(); + bundle.putString("URL", requestedUri.toString()); + Message webRequest = Message.obtain(); + webRequest.replyTo = internetHelperListener; + webRequest.setData(bundle); + try { + latch = new CountDownLatch(1); //the messenger should run on a single thread, hence we don't need to be worried about concurrency. This approach however is certainly not ideal. + internetHelper.send(webRequest); + latch.await(); + return internetResponse; + + } catch (RemoteException | InterruptedException e) { + e.printStackTrace(); + } + + } else { + LOG.debug("WEBVIEW request to openweathermap.org detected of type: " + requestedUri.getPath() + " params: " + requestedUri.getQuery()); + return mimicOpenWeatherMapResponse(requestedUri.getPath(), requestedUri.getQueryParameter("units")); + } + } else { + LOG.debug("WEBVIEW request:" + requestedUri.toString() + " not intercepted"); + } + return null; + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + Uri parsedUri = Uri.parse(url); + + if (parsedUri.getScheme().startsWith("http")) { + Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + GBApplication.getContext().startActivity(i); + } else if (parsedUri.getScheme().startsWith("pebblejs")) { + url = url.replaceFirst("^pebblejs://close#", "file:///android_asset/app_config/configure.html?config=true&json="); + view.loadUrl(url); + } else if (parsedUri.getScheme().equals("data")) { //clay + view.loadUrl(url); + } else { + LOG.debug("WEBVIEW Ignoring unhandled scheme: " + parsedUri.getScheme()); + } + + return true; + } + + private static WebResourceResponse mimicOpenWeatherMapResponse(String type, String units) { + + if (Weather.getInstance() == null || Weather.getInstance().getWeather2() == null) { + LOG.warn("WEBVIEW - Weather instance is null, cannot update weather"); + return null; + } + + CurrentPosition currentPosition = new CurrentPosition(); + + try { + JSONObject resp; + + if ("/data/2.5/weather".equals(type) && Weather.getInstance().getWeather2().reconstructedWeather != null) { + resp = new JSONObject(Weather.getInstance().getWeather2().reconstructedWeather.toString()); + + JSONObject main = resp.getJSONObject("main"); + + convertTemps(main, units); //caller might want different units + + resp.put("cod", 200); + resp.put("coord", coordObject(currentPosition)); + resp.put("sys", sysObject(currentPosition)); +// } else if ("/data/2.5/forecast".equals(type) && Weather.getInstance().getWeather2().reconstructedForecast != null) { //this is wrong, as we only have daily data. Unfortunately it looks like daily forecasts cannot be reconstructed +// resp = new JSONObject(Weather.getInstance().getWeather2().reconstructedForecast.toString()); +// +// JSONObject city = resp.getJSONObject("city"); +// city.put("coord", coordObject(currentPosition)); +// +// JSONArray list = resp.getJSONArray("list"); +// for (int i = 0, size = list.length(); i < size; i++) { +// JSONObject item = list.getJSONObject(i); +// JSONObject main = item.getJSONObject("main"); +// convertTemps(main, units); //caller might want different units +// } +// +// resp.put("cod", 200); + } else { + LOG.warn("WEBVIEW - cannot mimick request of type " + type + " (unsupported or lack of data)"); + return null; + } + + LOG.info("WEBVIEW - mimic openweather response" + resp.toString()); + Map headers = new HashMap<>(); + headers.put("Access-Control-Allow-Origin", "*"); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return new WebResourceResponse("application/json", "utf-8", 200, "OK", + headers, + new ByteArrayInputStream(resp.toString().getBytes()) + ); + } else { + return new WebResourceResponse("application/json", "utf-8", new ByteArrayInputStream(resp.toString().getBytes())); + } + } catch (JSONException e) { + LOG.warn(e.getMessage()); + } + + return null; + + } + + + private static JSONObject sysObject(CurrentPosition currentPosition) throws JSONException { + GregorianCalendar[] sunrise = SPA.calculateSunriseTransitSet(new GregorianCalendar(), currentPosition.getLatitude(), currentPosition.getLongitude(), DeltaT.estimate(new GregorianCalendar())); + + JSONObject sys = new JSONObject(); + sys.put("country", "World"); + sys.put("sunrise", (sunrise[0].getTimeInMillis() / 1000)); + sys.put("sunset", (sunrise[2].getTimeInMillis() / 1000)); + + return sys; + } + + private static void convertTemps(JSONObject main, String units) throws JSONException { + if ("metric".equals(units)) { + main.put("temp", (int) main.get("temp") - 273); + main.put("temp_min", (int) main.get("temp_min") - 273); + main.put("temp_max", (int) main.get("temp_max") - 273); + } else if ("imperial".equals(units)) { //it's 2017... this is so sad + main.put("temp", ((int) (main.get("temp")) - 273.15f) * 1.8f + 32); + main.put("temp_min", ((int) (main.get("temp_min")) - 273.15f) * 1.8f + 32); + main.put("temp_max", ((int) (main.get("temp_max")) - 273.15f) * 1.8f + 32); + } + } + + private static JSONObject coordObject(CurrentPosition currentPosition) throws JSONException { + JSONObject coord = new JSONObject(); + coord.put("lat", currentPosition.getLatitude()); + coord.put("lon", currentPosition.getLongitude()); + return coord; + } + +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/JSInterface.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/JSInterface.java new file mode 100644 index 000000000..5500c0a68 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/JSInterface.java @@ -0,0 +1,233 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview; + +import android.webkit.JavascriptInterface; +import android.widget.Toast; + +import org.json.JSONException; +import org.json.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Iterator; +import java.util.Scanner; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; +import nodomain.freeyourgadget.gadgetbridge.util.GB; +import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils; +import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton; + +public class JSInterface { + + private UUID mUuid; + private GBDevice device; + private Integer lastTransaction; + + private static final Logger LOG = LoggerFactory.getLogger(JSInterface.class); + + public JSInterface(GBDevice device, UUID mUuid) { + LOG.debug("Creating JS interface for UUID: " + mUuid.toString()); + this.device = device; + this.mUuid = mUuid; + this.lastTransaction = 0; + } + + + private boolean isLocationEnabledForWatchApp() { + return true; //FIXME: as long as we don't give watchapp internet access it's not a problem + } + + @JavascriptInterface + public void gbLog(String msg) { + LOG.debug("WEBVIEW webpage log: " + msg); + } + + @JavascriptInterface + public String sendAppMessage(String msg, String needsTransactionMsg) { + boolean needsTransaction = "true".equals(needsTransactionMsg); + LOG.debug("from WEBVIEW: " + msg + " needs a transaction: " + needsTransaction); + JSONObject knownKeys = WebViewSingleton.getAppConfigurationKeys(this.mUuid); + + try { + JSONObject in = new JSONObject(msg); + JSONObject out = new JSONObject(); + String inKey, outKey; + boolean passKey; + for (Iterator key = in.keys(); key.hasNext(); ) { + passKey = false; + inKey = key.next(); + outKey = null; + int pebbleAppIndex = knownKeys.optInt(inKey, -1); + if (pebbleAppIndex != -1) { + passKey = true; + outKey = String.valueOf(pebbleAppIndex); + } else { + //do not discard integer keys (see https://developer.pebble.com/guides/communication/using-pebblekit-js/ ) + Scanner scanner = new Scanner(inKey); + if (scanner.hasNextInt() && inKey.equals("" + scanner.nextInt())) { + passKey = true; + outKey = inKey; + } + } + + if (passKey) { + Object obj = in.get(inKey); + out.put(outKey, obj); + } else { + GB.toast("Discarded key " + inKey + ", not found in the local configuration and is not an integer key.", Toast.LENGTH_SHORT, GB.WARN); + } + + } + LOG.info("WEBVIEW message to pebble: " + out.toString()); + if (needsTransaction) { + this.lastTransaction++; + GBApplication.deviceService().onAppConfiguration(this.mUuid, out.toString(), this.lastTransaction); + return this.lastTransaction.toString(); + } else { + GBApplication.deviceService().onAppConfiguration(this.mUuid, out.toString(), null); + } + + } catch (JSONException e) { + LOG.warn(e.getMessage()); + } + return null; + } + + @JavascriptInterface + public String getActiveWatchInfo() { + JSONObject wi = new JSONObject(); + try { + wi.put("firmware", device.getFirmwareVersion()); + wi.put("platform", PebbleUtils.getPlatformName(device.getModel())); + wi.put("model", PebbleUtils.getModel(device.getModel())); + //TODO: use real info + wi.put("language", "en"); + } catch (JSONException e) { + LOG.warn(e.getMessage()); + } + //Json not supported apparently, we need to cast back and forth + return wi.toString(); + } + + @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"); + if (configurationFile.exists()) { + return "file:///" + configurationFile.getAbsolutePath(); + } + } catch (IOException e) { + LOG.warn(e.getMessage()); + } + return null; + } + + @JavascriptInterface + public String getAppStoredPreset() { + try { + File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); + File configurationFile = new File(destDir, this.mUuid.toString() + "_preset.json"); + if (configurationFile.exists()) { + return FileUtils.getStringFromFile(configurationFile); + } + } catch (IOException e) { + GB.toast("Error reading presets", Toast.LENGTH_LONG, GB.ERROR); + LOG.warn(e.getMessage()); + } + return null; + } + + @JavascriptInterface + public void saveAppStoredPreset(String msg) { + Writer writer; + + try { + File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); + File presetsFile = new File(destDir, this.mUuid.toString() + "_preset.json"); + writer = new BufferedWriter(new FileWriter(presetsFile)); + writer.write(msg); + writer.close(); + GB.toast("Presets stored", Toast.LENGTH_SHORT, GB.INFO); + } catch (IOException e) { + GB.toast("Error storing presets", Toast.LENGTH_LONG, GB.ERROR); + LOG.warn(e.getMessage()); + } + } + + @JavascriptInterface + public String getAppUUID() { + return this.mUuid.toString(); + } + + @JavascriptInterface + public String getAppLocalstoragePrefix() { + String prefix = device.getAddress() + this.mUuid.toString(); + try { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + byte[] bytes = prefix.getBytes("UTF-8"); + digest.update(bytes, 0, bytes.length); + bytes = digest.digest(); + final StringBuilder sb = new StringBuilder(); + for (byte aByte : bytes) { + sb.append(String.format("%02X", aByte)); + } + return sb.toString().toLowerCase(); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { + LOG.warn(e.getMessage()); + return prefix; + } + } + + @JavascriptInterface + public String getWatchToken() { + //specification says: A string that is guaranteed to be identical for each Pebble device for the same app across different mobile devices. The token is unique to your app and cannot be used to track Pebble devices across applications. see https://developer.pebble.com/docs/js/Pebble/ + return "gb" + this.mUuid.toString(); + } + + + @JavascriptInterface + public String getCurrentPosition() { + if (!isLocationEnabledForWatchApp()) { + return ""; + } + //we need to override this because the coarse location is not enough for the android webview, we should add the permission for fine location. + JSONObject geoPosition = new JSONObject(); + JSONObject coords = new JSONObject(); + try { + + CurrentPosition currentPosition = new CurrentPosition(); + + geoPosition.put("timestamp", currentPosition.timestamp); + + coords.put("latitude", currentPosition.latitude); + coords.put("longitude", currentPosition.longitude); + coords.put("accuracy", currentPosition.accuracy); + coords.put("altitude", currentPosition.altitude); + coords.put("speed", currentPosition.speed); + + geoPosition.put("coords", coords); + + } catch (JSONException e) { + LOG.warn(e.getMessage()); + } + LOG.info("WEBVIEW - geo position" + geoPosition.toString()); + return geoPosition.toString(); + } + + @JavascriptInterface + public void eventFinished(String event) { + LOG.debug("WEBVIEW event finished: " + event); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java index 4f1201cc3..3c43aebfc 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java @@ -281,19 +281,4 @@ public class DeviceHelper { return false; } - /** - * Returns true if the background webview for executing javascript is needed - * for any of the known/available devices. - * @param context - */ - public boolean needsBackgroundWebView(Context context) { - Set availableDevices = getAvailableDevices(context); - for (GBDevice device : availableDevices) { - DeviceCoordinator coordinator = getCoordinator(device); - if (coordinator.needsBackgroundWebView(device)) { - return true; - } - } - return false; - } } 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 66823b191..b8d675dd5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -1,18 +1,11 @@ package nodomain.freeyourgadget.gadgetbridge.util; -import android.Manifest; -import android.annotation.TargetApi; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.MutableContextWrapper; import android.content.ServiceConnection; -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.Bundle; import android.os.Handler; @@ -20,23 +13,12 @@ import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Messenger; -import android.os.RemoteException; import android.support.annotation.NonNull; -import android.support.v4.app.ActivityCompat; import android.util.SparseArray; -import android.webkit.ConsoleMessage; -import android.webkit.JavascriptInterface; import android.webkit.ValueCallback; -import android.webkit.WebChromeClient; -import android.webkit.WebResourceRequest; import android.webkit.WebResourceResponse; import android.webkit.WebSettings; import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.Toast; - -import net.e175.klaus.solarpositioning.DeltaT; -import net.e175.klaus.solarpositioning.SPA; import org.json.JSONArray; import org.json.JSONException; @@ -44,27 +26,20 @@ import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileWriter; import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.io.Writer; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.Map; -import java.util.Scanner; import java.util.UUID; import java.util.concurrent.CountDownLatch; -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.service.devices.pebble.webview.GBChromeClient; +import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.GBWebClient; +import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.JSInterface; public class WebViewSingleton { @@ -75,11 +50,11 @@ public class WebViewSingleton { private Looper mainLooper; private static WebViewSingleton webViewSingleton = new WebViewSingleton(); private static UUID currentRunningUUID; - private static Messenger internetHelper = null; - private static boolean internetHelperBound; - private static CountDownLatch latch; - private static WebResourceResponse internetResponse; - private final static Messenger internetHelperListener = new Messenger(new IncomingHandler()); + public static Messenger internetHelper = null; + public static boolean internetHelperBound; + public static CountDownLatch latch; + public static WebResourceResponse internetResponse; + public final static Messenger internetHelperListener = new Messenger(new IncomingHandler()); private WebViewSingleton() { } @@ -120,7 +95,7 @@ public class WebViewSingleton { }; //Internet helper inbound (responses) handler - static class IncomingHandler extends Handler { + private static class IncomingHandler extends Handler { @Override public void handleMessage(Message msg) { Bundle data = msg.getData(); @@ -141,14 +116,9 @@ public class WebViewSingleton { } } - public static void updateActivityContext(Activity context) { - if (context != null) { - webViewSingleton.contextWrapper.setBaseContext(context); - } - } - @NonNull - public static WebView getWebView() { + public static WebView getWebView(Context context) { + webViewSingleton.contextWrapper.setBaseContext(context); return webViewSingleton.instance; } @@ -242,229 +212,7 @@ public class WebViewSingleton { }); } - private static class CurrentPosition { - long timestamp; - double altitude; - float latitude, longitude, accuracy, speed; - - private CurrentPosition() { - Prefs prefs = GBApplication.getPrefs(); - this.latitude = prefs.getFloat("location_latitude", 0); - this.longitude = prefs.getFloat("location_longitude", 0); - LOG.info("got longitude/latitude from preferences: " + latitude + "/" + longitude); - - this.timestamp = System.currentTimeMillis() - 86400000; //let accessor know this value is really old - - if (ActivityCompat.checkSelfPermission(GBApplication.getContext(), Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED && - prefs.getBoolean("use_updated_location_if_available", false)) { - LocationManager locationManager = (LocationManager) GBApplication.getContext().getSystemService(Context.LOCATION_SERVICE); - Criteria criteria = new Criteria(); - String provider = locationManager.getBestProvider(criteria, false); - if (provider != null) { - Location lastKnownLocation = locationManager.getLastKnownLocation(provider); - if (lastKnownLocation != null) { - this.timestamp = lastKnownLocation.getTime(); - this.timestamp = System.currentTimeMillis() - 1000; //TODO: request updating the location and don't fake its age - - this.latitude = (float) lastKnownLocation.getLatitude(); - this.longitude = (float) lastKnownLocation.getLongitude(); - this.accuracy = lastKnownLocation.getAccuracy(); - this.altitude = (float) lastKnownLocation.getAltitude(); - this.speed = lastKnownLocation.getSpeed(); - } - } - } - } - } - - private static WebResourceResponse mimicKiezelPayResponse() { - return null; - } - - private static JSONObject coordObject(CurrentPosition currentPosition) throws JSONException { - JSONObject coord = new JSONObject(); - coord.put("lat", currentPosition.latitude); - coord.put("lon", currentPosition.longitude); - return coord; - } - - private static JSONObject sysObject(CurrentPosition currentPosition) throws JSONException { - GregorianCalendar[] sunrise = SPA.calculateSunriseTransitSet(new GregorianCalendar(), currentPosition.latitude, currentPosition.longitude, DeltaT.estimate(new GregorianCalendar())); - - JSONObject sys = new JSONObject(); - sys.put("country", "World"); - sys.put("sunrise", (sunrise[0].getTimeInMillis() / 1000)); - sys.put("sunset", (sunrise[2].getTimeInMillis() / 1000)); - - return sys; - } - - private static void convertTemps(JSONObject main, String units) throws JSONException { - if ("metric".equals(units)) { - main.put("temp", (int) main.get("temp") - 273); - main.put("temp_min", (int) main.get("temp_min") - 273); - main.put("temp_max", (int) main.get("temp_max") - 273); - } else if ("imperial".equals(units)) { //it's 2017... this is so sad - main.put("temp", ((int) (main.get("temp")) - 273.15f) * 1.8f + 32); - main.put("temp_min", ((int) (main.get("temp_min")) - 273.15f) * 1.8f + 32); - main.put("temp_max", ((int) (main.get("temp_max")) - 273.15f) * 1.8f + 32); - } - } - - private static WebResourceResponse mimicOpenWeatherMapResponse(String type, String units) { - - if (Weather.getInstance() == null || Weather.getInstance().getWeather2() == null) { - LOG.warn("WEBVIEW - Weather instance is null, cannot update weather"); - return null; - } - - CurrentPosition currentPosition = new CurrentPosition(); - - try { - JSONObject resp; - - if ("/data/2.5/weather".equals(type) && Weather.getInstance().getWeather2().reconstructedWeather != null) { - resp = new JSONObject(Weather.getInstance().getWeather2().reconstructedWeather.toString()); - - JSONObject main = resp.getJSONObject("main"); - - convertTemps(main, units); //caller might want different units - - resp.put("cod", 200); - resp.put("coord", coordObject(currentPosition)); - resp.put("sys", sysObject(currentPosition)); -// } else if ("/data/2.5/forecast".equals(type) && Weather.getInstance().getWeather2().reconstructedForecast != null) { //this is wrong, as we only have daily data. Unfortunately it looks like daily forecasts cannot be reconstructed -// resp = new JSONObject(Weather.getInstance().getWeather2().reconstructedForecast.toString()); -// -// JSONObject city = resp.getJSONObject("city"); -// city.put("coord", coordObject(currentPosition)); -// -// JSONArray list = resp.getJSONArray("list"); -// for (int i = 0, size = list.length(); i < size; i++) { -// JSONObject item = list.getJSONObject(i); -// JSONObject main = item.getJSONObject("main"); -// convertTemps(main, units); //caller might want different units -// } -// -// resp.put("cod", 200); - } else { - LOG.warn("WEBVIEW - cannot mimick request of type " + type + " (unsupported or lack of data)"); - return null; - } - - LOG.info("WEBVIEW - mimic openweather response" + resp.toString()); - Map headers = new HashMap<>(); - headers.put("Access-Control-Allow-Origin", "*"); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - return new WebResourceResponse("application/json", "utf-8", 200, "OK", - headers, - new ByteArrayInputStream(resp.toString().getBytes()) - ); - } else { - return new WebResourceResponse("application/json", "utf-8", new ByteArrayInputStream(resp.toString().getBytes())); - } - } catch (JSONException e) { - LOG.warn(e.getMessage()); - } - - return null; - - } - - private static class GBChromeClient extends WebChromeClient { - @Override - public boolean onConsoleMessage(ConsoleMessage consoleMessage) { - if (ConsoleMessage.MessageLevel.ERROR.equals(consoleMessage.messageLevel())) { - GB.toast(formatConsoleMessage(consoleMessage), Toast.LENGTH_LONG, GB.ERROR); - //TODO: show error page - } - return super.onConsoleMessage(consoleMessage); - } - - } - - private static String formatConsoleMessage(ConsoleMessage message) { - String sourceId = message.sourceId(); - if (sourceId == null || sourceId.length() == 0) { - sourceId = "unknown"; - } - return String.format("%s (at %s: %d)", message.message(), sourceId, message.lineNumber()); - } - - private static class GBWebClient extends WebViewClient { - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - @Override - public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { - Uri parsedUri = request.getUrl(); - LOG.debug("WEBVIEW shouldInterceptRequest URL: " + parsedUri.toString()); - WebResourceResponse mimickedReply = mimicReply(parsedUri); - if (mimickedReply != null) - return mimickedReply; - return super.shouldInterceptRequest(view, request); - } - - @Override - public WebResourceResponse shouldInterceptRequest(WebView view, String url) { - LOG.debug("WEBVIEW shouldInterceptRequest URL (legacy): " + url); - Uri parsedUri = Uri.parse(url); - WebResourceResponse mimickedReply = mimicReply(parsedUri); - if (mimickedReply != null) - return mimickedReply; - return super.shouldInterceptRequest(view, url); - } - - private WebResourceResponse mimicReply(Uri requestedUri) { - if (requestedUri.getHost() != null && (requestedUri.getHost().contains("openweathermap.org") || requestedUri.getHost().contains("tagesschau.de") )) { - if (internetHelperBound) { - LOG.debug("WEBVIEW forwarding request to the internet helper"); - Bundle bundle = new Bundle(); - bundle.putString("URL", requestedUri.toString()); - Message webRequest = Message.obtain(); - webRequest.replyTo = internetHelperListener; - webRequest.setData(bundle); - try { - latch = new CountDownLatch(1); //the messenger should run on a single thread, hence we don't need to be worried about concurrency. This approach however is certainly not ideal. - internetHelper.send(webRequest); - latch.await(); - return internetResponse; - - } catch (RemoteException | InterruptedException e) { - e.printStackTrace(); - } - - } else { - LOG.debug("WEBVIEW request to openweathermap.org detected of type: " + requestedUri.getPath() + " params: " + requestedUri.getQuery()); - return mimicOpenWeatherMapResponse(requestedUri.getPath(), requestedUri.getQueryParameter("units")); - } - } else { - LOG.debug("WEBVIEW request:" + requestedUri.toString() + " not intercepted"); - } - return null; - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - Uri parsedUri = Uri.parse(url); - - if (parsedUri.getScheme().startsWith("http")) { - Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); - GBApplication.getContext().startActivity(i); - } else if (parsedUri.getScheme().startsWith("pebblejs")) { - url = url.replaceFirst("^pebblejs://close#", "file:///android_asset/app_config/configure.html?config=true&json="); - view.loadUrl(url); - } else if (parsedUri.getScheme().equals("data")) { //clay - view.loadUrl(url); - } else { - LOG.debug("WEBVIEW Ignoring unhandled scheme: " + parsedUri.getScheme()); - } - - return true; - } - } - - private static JSONObject getAppConfigurationKeys(UUID uuid) { + public static JSONObject getAppConfigurationKeys(UUID uuid) { try { File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); File configurationFile = new File(destDir, uuid.toString() + ".json"); @@ -525,207 +273,4 @@ public class WebViewSingleton { return jsAppMessage.toString(); } - private static class JSInterface { - - UUID mUuid; - GBDevice device; - Integer lastTransaction; - - private JSInterface(GBDevice device, UUID mUuid) { - LOG.debug("Creating JS interface for UUID: " + mUuid.toString()); - this.device = device; - this.mUuid = mUuid; - this.lastTransaction = 0; - } - - - private boolean isLocationEnabledForWatchApp() { - return true; //as long as we don't give watchapp internet access it's not a problem - } - - @JavascriptInterface - public void gbLog(String msg) { - LOG.debug("WEBVIEW webpage log: " + msg); - } - - @JavascriptInterface - public String sendAppMessage(String msg, String needsTransactionMsg) { - boolean needsTransaction = "true".equals(needsTransactionMsg); - LOG.debug("from WEBVIEW: " + msg + " needs a transaction: " + needsTransaction); - JSONObject knownKeys = getAppConfigurationKeys(this.mUuid); - - try { - JSONObject in = new JSONObject(msg); - JSONObject out = new JSONObject(); - String inKey, outKey; - boolean passKey; - for (Iterator key = in.keys(); key.hasNext(); ) { - passKey = false; - inKey = key.next(); - outKey = null; - int pebbleAppIndex = knownKeys.optInt(inKey, -1); - if (pebbleAppIndex != -1) { - passKey = true; - outKey = String.valueOf(pebbleAppIndex); - } else { - //do not discard integer keys (see https://developer.pebble.com/guides/communication/using-pebblekit-js/ ) - Scanner scanner = new Scanner(inKey); - if (scanner.hasNextInt() && inKey.equals("" + scanner.nextInt())) { - passKey = true; - outKey = inKey; - } - } - - if (passKey) { - Object obj = in.get(inKey); - out.put(outKey, obj); - } else { - GB.toast("Discarded key " + inKey + ", not found in the local configuration and is not an integer key.", Toast.LENGTH_SHORT, GB.WARN); - } - - } - LOG.info("WEBVIEW message to pebble: " + out.toString()); - if (needsTransaction) { - this.lastTransaction++; - GBApplication.deviceService().onAppConfiguration(this.mUuid, out.toString(), this.lastTransaction); - return this.lastTransaction.toString(); - } else { - GBApplication.deviceService().onAppConfiguration(this.mUuid, out.toString(), null); - } - - } catch (JSONException e) { - LOG.warn(e.getMessage()); - } - return null; - } - - @JavascriptInterface - public String getActiveWatchInfo() { - JSONObject wi = new JSONObject(); - try { - wi.put("firmware", device.getFirmwareVersion()); - wi.put("platform", PebbleUtils.getPlatformName(device.getModel())); - wi.put("model", PebbleUtils.getModel(device.getModel())); - //TODO: use real info - wi.put("language", "en"); - } catch (JSONException e) { - LOG.warn(e.getMessage()); - } - //Json not supported apparently, we need to cast back and forth - return wi.toString(); - } - - @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"); - if (configurationFile.exists()) { - return "file:///" + configurationFile.getAbsolutePath(); - } - } catch (IOException e) { - LOG.warn(e.getMessage()); - } - return null; - } - - @JavascriptInterface - public String getAppStoredPreset() { - try { - File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); - File configurationFile = new File(destDir, this.mUuid.toString() + "_preset.json"); - if (configurationFile.exists()) { - return FileUtils.getStringFromFile(configurationFile); - } - } catch (IOException e) { - GB.toast("Error reading presets", Toast.LENGTH_LONG, GB.ERROR); - LOG.warn(e.getMessage()); - } - return null; - } - - @JavascriptInterface - public void saveAppStoredPreset(String msg) { - Writer writer; - - try { - File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache"); - File presetsFile = new File(destDir, this.mUuid.toString() + "_preset.json"); - writer = new BufferedWriter(new FileWriter(presetsFile)); - writer.write(msg); - writer.close(); - GB.toast("Presets stored", Toast.LENGTH_SHORT, GB.INFO); - } catch (IOException e) { - GB.toast("Error storing presets", Toast.LENGTH_LONG, GB.ERROR); - LOG.warn(e.getMessage()); - } - } - - @JavascriptInterface - public String getAppUUID() { - return this.mUuid.toString(); - } - - @JavascriptInterface - public String getAppLocalstoragePrefix() { - String prefix = device.getAddress() + this.mUuid.toString(); - try { - MessageDigest digest = MessageDigest.getInstance("SHA-1"); - byte[] bytes = prefix.getBytes("UTF-8"); - digest.update(bytes, 0, bytes.length); - bytes = digest.digest(); - final StringBuilder sb = new StringBuilder(); - for (byte aByte : bytes) { - sb.append(String.format("%02X", aByte)); - } - return sb.toString().toLowerCase(); - } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { - LOG.warn(e.getMessage()); - return prefix; - } - } - - @JavascriptInterface - public String getWatchToken() { - //specification says: A string that is guaranteed to be identical for each Pebble device for the same app across different mobile devices. The token is unique to your app and cannot be used to track Pebble devices across applications. see https://developer.pebble.com/docs/js/Pebble/ - return "gb" + this.mUuid.toString(); - } - - - @JavascriptInterface - public String getCurrentPosition() { - if (!isLocationEnabledForWatchApp()) { - return ""; - } - //we need to override this because the coarse location is not enough for the android webview, we should add the permission for fine location. - JSONObject geoPosition = new JSONObject(); - JSONObject coords = new JSONObject(); - try { - - CurrentPosition currentPosition = new CurrentPosition(); - - geoPosition.put("timestamp", currentPosition.timestamp); - - coords.put("latitude", currentPosition.latitude); - coords.put("longitude", currentPosition.longitude); - coords.put("accuracy", currentPosition.accuracy); - coords.put("altitude", currentPosition.altitude); - coords.put("speed", currentPosition.speed); - - geoPosition.put("coords", coords); - - } catch (JSONException e) { - LOG.warn(e.getMessage()); - } - LOG.info("WEBVIEW - geo position" + geoPosition.toString()); - return geoPosition.toString(); - } - - @JavascriptInterface - public void eventFinished(String event) { - LOG.debug("WEBVIEW event finished: " + event); - } - } - } diff --git a/app/src/main/res/layout/activity_legacy_external_pebble_js.xml b/app/src/main/res/layout/activity_legacy_external_pebble_js.xml new file mode 100644 index 000000000..551fb9276 --- /dev/null +++ b/app/src/main/res/layout/activity_legacy_external_pebble_js.xml @@ -0,0 +1,5 @@ + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c12fce176..c1eeb88c3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -170,6 +170,8 @@ Will cause logs from watch apps to be logged by Gadgetbridge (requires reconnect) Prematurely ACK PebbleKit Will cause messages that are sent to external 3rd party apps to be acknowledged always and immediately + Enable background JS + When enabled, allows watchfaces to show weather, battery info etc. Reconnection attempts diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index e4cc55818..2922198cb 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -412,6 +412,11 @@ android:key="pebble_always_ack_pebblekit" android:summary="@string/pref_summary_pebble_always_ack_pebblekit" android:title="@string/pref_title_pebble_always_ack_pebblekit" /> + Date: Mon, 25 Sep 2017 23:00:39 +0200 Subject: [PATCH 43/48] Just a single method rename, to make it more clear --- .../gadgetbridge/activities/ExternalPebbleJSActivity.java | 2 +- .../freeyourgadget/gadgetbridge/util/WebViewSingleton.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) 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 5970eafc1..0177314cf 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java @@ -69,7 +69,7 @@ public class ExternalPebbleJSActivity extends AbstractGBActivity { if (extras.getBoolean(SHOW_CONFIG, false)) { WebViewSingleton.runJavascriptInterface(currentDevice, currentUUID); } else if (extras.getBoolean(START_BG_WEBVIEW, false)) { - WebViewSingleton.getInstance(this); + WebViewSingleton.ensureCreated(this); finish(); } } 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 b8d675dd5..4c7defd44 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -59,7 +59,7 @@ public class WebViewSingleton { private WebViewSingleton() { } - public static synchronized WebView getInstance(Activity context) { + public static synchronized void ensureCreated(Activity context) { if (webViewSingleton.instance == null) { webViewSingleton.contextWrapper = new MutableContextWrapper(context); webViewSingleton.mainLooper = context.getMainLooper(); @@ -76,7 +76,6 @@ public class WebViewSingleton { //needed for localstorage webSettings.setDatabaseEnabled(true); } - return webViewSingleton.instance; } //Internet helper outgoing connection From 0eb8a0b5e2284b79c6ddb03fdf4f03808ddc3656 Mon Sep 17 00:00:00 2001 From: Andreas Shimokawa Date: Wed, 4 Oct 2017 23:12:49 +0200 Subject: [PATCH 44/48] Pebble: Fix background javascript for Pebble2/LE/Emulator --- .../service/devices/pebble/PebbleIoThread.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) 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 e7ce17c63..9738b05bb 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 @@ -162,16 +162,15 @@ class PebbleIoThread extends GBDeviceIoThread { mBtSocket.connect(); mInStream = mBtSocket.getInputStream(); mOutStream = mBtSocket.getOutputStream(); - - if (prefs.getBoolean("pebble_enable_background_javascript", false)) { - Intent startIntent = new Intent(getContext(), ExternalPebbleJSActivity.class); - startIntent.putExtra(ExternalPebbleJSActivity.START_BG_WEBVIEW, true); - getContext().startActivity(startIntent); - } else { - LOG.debug("Not enabling background Webview, is disabled in preferences."); - } } } + if (prefs.getBoolean("pebble_enable_background_javascript", false)) { + Intent startIntent = new Intent(getContext(), ExternalPebbleJSActivity.class); + startIntent.putExtra(ExternalPebbleJSActivity.START_BG_WEBVIEW, true); + getContext().startActivity(startIntent); + } else { + LOG.debug("Not enabling background Webview, is disabled in preferences."); + } } catch (IOException e) { LOG.warn("error while connecting: " + e.getMessage(), e); gbDevice.setState(originalState); From e99b802a3525c47088fff5ec207eb1b84476aa08 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Tue, 10 Oct 2017 22:00:01 +0200 Subject: [PATCH 45/48] Pebble: enable toggle background JS only if experimental features are enabled --- app/src/main/res/xml/preferences.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 804c477f4..58b2b2597 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -422,6 +422,7 @@ Date: Tue, 10 Oct 2017 22:10:26 +0200 Subject: [PATCH 46/48] Pebble: enable legacy handlers if background JS is disabled --- .../devices/pebble/PebbleProtocol.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) 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 f1e6ddba4..0c2856361 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 @@ -405,16 +405,18 @@ 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_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)); + if (!(GBApplication.getPrefs().getBoolean("pebble_enable_background_javascript", false))) { + 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)); + } } private final HashMap mDatalogSessions = new HashMap<>(); From c4f36d1202f0b7e892f74fabe32c76533277ec7a Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Fri, 13 Oct 2017 21:57:22 +0200 Subject: [PATCH 47/48] Pebble: background webview, address (most of) review comments --- .../activities/ExternalPebbleJSActivity.java | 1 + .../devices/pebble/webview/GBWebClient.java | 4 ++-- .../devices/pebble/webview/JSInterface.java | 12 +++++----- .../gadgetbridge/util/WebViewSingleton.java | 24 +++++++++++++++---- 4 files changed, 28 insertions(+), 13 deletions(-) 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 0177314cf..699c786a5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ExternalPebbleJSActivity.java @@ -71,6 +71,7 @@ public class ExternalPebbleJSActivity extends AbstractGBActivity { } else if (extras.getBoolean(START_BG_WEBVIEW, false)) { WebViewSingleton.ensureCreated(this); finish(); + return; } } } else { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java index 5962ef236..5ae09438b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java @@ -76,7 +76,7 @@ public class GBWebClient extends WebViewClient { return internetResponse; } catch (RemoteException | InterruptedException e) { - e.printStackTrace(); + LOG.warn("Error downloading data from " + requestedUri, e); } } else { @@ -163,7 +163,7 @@ public class GBWebClient extends WebViewClient { return new WebResourceResponse("application/json", "utf-8", new ByteArrayInputStream(resp.toString().getBytes())); } } catch (JSONException e) { - LOG.warn(e.getMessage()); + LOG.warn("Error building the JSON weather message.", e); } return null; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/JSInterface.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/JSInterface.java index 5500c0a68..f3dd5adb0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/JSInterface.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/JSInterface.java @@ -98,7 +98,7 @@ public class JSInterface { } } catch (JSONException e) { - LOG.warn(e.getMessage()); + LOG.warn("Error building the appmessage JSON object", e); } return null; } @@ -113,7 +113,7 @@ public class JSInterface { //TODO: use real info wi.put("language", "en"); } catch (JSONException e) { - LOG.warn(e.getMessage()); + LOG.warn("Error building the ActiveWathcInfo JSON object", e); } //Json not supported apparently, we need to cast back and forth return wi.toString(); @@ -129,7 +129,7 @@ public class JSInterface { return "file:///" + configurationFile.getAbsolutePath(); } } catch (IOException e) { - LOG.warn(e.getMessage()); + LOG.warn("Error loading config file", e); } return null; } @@ -144,7 +144,7 @@ public class JSInterface { } } catch (IOException e) { GB.toast("Error reading presets", Toast.LENGTH_LONG, GB.ERROR); - LOG.warn(e.getMessage()); + LOG.warn("Error reading presets", e); } return null; } @@ -162,7 +162,7 @@ public class JSInterface { GB.toast("Presets stored", Toast.LENGTH_SHORT, GB.INFO); } catch (IOException e) { GB.toast("Error storing presets", Toast.LENGTH_LONG, GB.ERROR); - LOG.warn(e.getMessage()); + LOG.warn("Error storing presets", e); } } @@ -185,7 +185,7 @@ public class JSInterface { } return sb.toString().toLowerCase(); } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { - LOG.warn(e.getMessage()); + LOG.warn("Error definining local storage prefix", e); return prefix; } } 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 4c7defd44..2a1ad57de 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/WebViewSingleton.java @@ -29,6 +29,7 @@ import org.slf4j.LoggerFactory; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; +import java.nio.charset.Charset; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -95,6 +96,16 @@ public class WebViewSingleton { //Internet helper inbound (responses) handler private static class IncomingHandler extends Handler { + + private String getCharsetFromHeaders(String contentType) { + if (contentType != null && contentType.toLowerCase().trim().contains("charset=")) { + String[] parts = contentType.toLowerCase().trim().split("="); + if (parts.length > 0) + return parts[1]; + } + return null; + } + @Override public void handleMessage(Message msg) { Bundle data = msg.getData(); @@ -105,10 +116,10 @@ public class WebViewSingleton { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { internetResponse = new WebResourceResponse(data.getString("content-type"), data.getString("content-encoding"), 200, "OK", headers, - new ByteArrayInputStream(data.getString("response").getBytes()) + new ByteArrayInputStream(data.getString("response").getBytes(Charset.forName(getCharsetFromHeaders(data.getString("content-type"))))) ); } else { - internetResponse = new WebResourceResponse(data.getString("content-type"), data.getString("content-encoding"), new ByteArrayInputStream(data.getString("response").getBytes())); + internetResponse = new WebResourceResponse(data.getString("content-type"), data.getString("content-encoding"), new ByteArrayInputStream(data.getString("response").getBytes(Charset.forName(getCharsetFromHeaders(data.getString("content-type")))))); } latch.countDown(); @@ -122,6 +133,9 @@ public class WebViewSingleton { } public static void runJavascriptInterface(GBDevice device, UUID uuid) { + if (uuid == null && device == null) { + throw new RuntimeException("Javascript interface started without device and uuid"); + } if (uuid.equals(currentRunningUUID)) { LOG.debug("WEBVIEW uuid not changed keeping the old context"); } else { @@ -221,7 +235,7 @@ public class WebViewSingleton { return json.getJSONObject("appKeys"); } } catch (IOException | JSONException e) { - LOG.warn(e.getMessage()); + LOG.warn("Unable to parse configuration JSON file", e); } return null; } @@ -232,7 +246,7 @@ public class WebViewSingleton { JSONObject knownKeys = getAppConfigurationKeys(uuid); SparseArray appKeysMap = new SparseArray<>(); - if (knownKeys == null) { + if (knownKeys == null || msg == null) { return "{}"; } @@ -267,7 +281,7 @@ public class WebViewSingleton { jsAppMessage.put("payload", outgoing); } catch (Exception e) { - LOG.warn(e.getMessage()); + LOG.warn("Unable to parse incoming app message", e); } return jsAppMessage.toString(); } From 2a0388f1c63ab1e1ab52cca80cd76980b50c3446 Mon Sep 17 00:00:00 2001 From: Daniele Gobbetti Date: Fri, 13 Oct 2017 22:16:01 +0200 Subject: [PATCH 48/48] Pebble: background webview add allowed domains array --- .../service/devices/pebble/webview/GBWebClient.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java index 5ae09438b..a018a6db0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/webview/GBWebClient.java @@ -37,6 +37,10 @@ import static nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton.latch; public class GBWebClient extends WebViewClient { + private String[] AllowedDomains = new String[]{ + "openweathermap.org", //for weather :) + "tagesschau.de" //for internal watchapp tests + }; private static final Logger LOG = LoggerFactory.getLogger(GBWebClient.class); @TargetApi(Build.VERSION_CODES.LOLLIPOP) @@ -60,8 +64,9 @@ public class GBWebClient extends WebViewClient { return super.shouldInterceptRequest(view, url); } + private WebResourceResponse mimicReply(Uri requestedUri) { - if (requestedUri.getHost() != null && (requestedUri.getHost().contains("openweathermap.org") || requestedUri.getHost().contains("tagesschau.de"))) { + if (requestedUri.getHost() != null && (org.apache.commons.lang3.StringUtils.indexOfAny(requestedUri.getHost(), AllowedDomains) != -1)) { if (internetHelperBound) { LOG.debug("WEBVIEW forwarding request to the internet helper"); Bundle bundle = new Bundle();