mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-29 13:26:50 +01:00
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
This commit is contained in:
parent
4ce890b5ce
commit
53d8f88670
@ -104,7 +104,7 @@ function gbPebble() {
|
|||||||
|
|
||||||
this.actuallySendData = function() {
|
this.actuallySendData = function() {
|
||||||
GBjs.sendAppMessage(self.configurationValues);
|
GBjs.sendAppMessage(self.configurationValues);
|
||||||
GBjs.closeActivity();
|
GBActivity.closeActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.savePreset = function() {
|
this.savePreset = function() {
|
||||||
@ -169,7 +169,7 @@ function gbPebble() {
|
|||||||
|
|
||||||
this.showConfiguration = function() {
|
this.showConfiguration = function() {
|
||||||
console.error("This watchapp doesn't support configuration");
|
console.error("This watchapp doesn't support configuration");
|
||||||
GBjs.closeActivity();
|
GBActivity.closeActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.parseReturnedPebbleJS = function() {
|
this.parseReturnedPebbleJS = function() {
|
||||||
@ -204,7 +204,6 @@ if (jsConfigFile != null) {
|
|||||||
if (json_string != '') {
|
if (json_string != '') {
|
||||||
Pebble.evaluate('webviewclosed',[t]);
|
Pebble.evaluate('webviewclosed',[t]);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (storedPreset === undefined) {
|
if (storedPreset === undefined) {
|
||||||
var presetElements = document.getElementsByClassName("load_presets");
|
var presetElements = document.getElementsByClassName("load_presets");
|
||||||
@ -212,7 +211,6 @@ if (jsConfigFile != null) {
|
|||||||
presetElements[i].style.display = 'none';
|
presetElements[i].style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Pebble.evaluate('showConfiguration');
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -3,42 +3,26 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.support.v4.app.NavUtils;
|
import android.support.v4.app.NavUtils;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.webkit.ConsoleMessage;
|
|
||||||
import android.webkit.JavascriptInterface;
|
import android.webkit.JavascriptInterface;
|
||||||
import android.webkit.WebChromeClient;
|
import android.webkit.ValueCallback;
|
||||||
import android.webkit.WebSettings;
|
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.webkit.WebViewClient;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
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 java.util.UUID;
|
||||||
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton;
|
||||||
|
|
||||||
public class ExternalPebbleJSActivity extends GBActivity {
|
public class ExternalPebbleJSActivity extends GBActivity {
|
||||||
|
|
||||||
@ -64,21 +48,8 @@ public class ExternalPebbleJSActivity extends GBActivity {
|
|||||||
|
|
||||||
setContentView(R.layout.activity_external_pebble_js);
|
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);
|
WebViewSingleton.getorInitWebView(this, mGBDevice, appUuid);
|
||||||
myWebView.addJavascriptInterface(gbJSInterface, "GBjs");
|
|
||||||
|
|
||||||
myWebView.loadUrl("file:///android_asset/app_config/configure.html");
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,6 +65,19 @@ public class ExternalPebbleJSActivity extends GBActivity {
|
|||||||
super.onResume();
|
super.onResume();
|
||||||
String queryString = "";
|
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) {
|
if (confUri != null) {
|
||||||
//getting back with configuration data
|
//getting back with configuration data
|
||||||
try {
|
try {
|
||||||
@ -103,209 +87,28 @@ public class ExternalPebbleJSActivity extends GBActivity {
|
|||||||
GB.toast("returned uri: " + confUri.toString(), Toast.LENGTH_LONG, GB.ERROR);
|
GB.toast("returned uri: " + confUri.toString(), Toast.LENGTH_LONG, GB.ERROR);
|
||||||
}
|
}
|
||||||
myWebView.loadUrl("file:///android_asset/app_config/configure.html?" + queryString);
|
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<String>() {
|
||||||
private JSONObject getAppConfigurationKeys() {
|
@Override
|
||||||
try {
|
public void onReceiveValue(String s) {
|
||||||
File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache");
|
LOG.debug("Callback from showConfiguration", s);
|
||||||
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 {
|
} else {
|
||||||
url = url.replaceFirst("^pebblejs://close#", "file:///android_asset/app_config/configure.html?config=true&json=");
|
myWebView.loadUrl("javascript:Pebble.evaluate('showConfiguration');");
|
||||||
view.loadUrl(url);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class JSInterface {
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
|
||||||
Context mContext;
|
FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder);
|
||||||
|
fl.removeAllViews();
|
||||||
public JSInterface(Context c) {
|
super.onPause();
|
||||||
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<String> 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -318,5 +121,18 @@ public class ExternalPebbleJSActivity extends GBActivity {
|
|||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ActivityJSInterface {
|
||||||
|
|
||||||
|
Context mContext;
|
||||||
|
|
||||||
|
public ActivityJSInterface(Context c) {
|
||||||
|
mContext = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JavascriptInterface
|
||||||
|
public void closeActivity() {
|
||||||
|
NavUtils.navigateUpFromSameTask((ExternalPebbleJSActivity) mContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
|
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.WebViewSingleton;
|
||||||
|
|
||||||
class PebbleIoThread extends GBDeviceIoThread {
|
class PebbleIoThread extends GBDeviceIoThread {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(PebbleIoThread.class);
|
private static final Logger LOG = LoggerFactory.getLogger(PebbleIoThread.class);
|
||||||
@ -429,6 +430,9 @@ class PebbleIoThread extends GBDeviceIoThread {
|
|||||||
} else {
|
} else {
|
||||||
gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT);
|
gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WebViewSingleton.disposeWebView();
|
||||||
|
|
||||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -565,6 +569,7 @@ class PebbleIoThread extends GBDeviceIoThread {
|
|||||||
break;
|
break;
|
||||||
case START:
|
case START:
|
||||||
LOG.info("got GBDeviceEventAppManagement START event for uuid: " + appMgmt.uuid);
|
LOG.info("got GBDeviceEventAppManagement START event for uuid: " + appMgmt.uuid);
|
||||||
|
WebViewSingleton.getorInitWebView(getContext(), gbDevice, appMgmt.uuid);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -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<String> 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout android:id="@+id/webview_placeholder"
|
||||||
android:id="@+id/configureWebview"
|
android:layout_height="match_parent"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="fill_parent" />
|
xmlns:android="http://schemas.android.com/apk/res/android" />
|
||||||
|
Loading…
Reference in New Issue
Block a user