mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-19 15:17:50 +01:00
Merge pull request #235 from Freeyourgadget/feature-configuration
Use external browser for configuring pebble apps
This commit is contained in:
commit
88982a6174
@ -53,6 +53,7 @@
|
||||
android:label="@string/preferences_miband_settings"
|
||||
android:parentActivityName=".activities.SettingsActivity" />
|
||||
<activity
|
||||
android:launchMode="singleTop"
|
||||
android:name=".activities.AppManagerActivity"
|
||||
android:label="@string/title_activity_appmanager"
|
||||
android:parentActivityName=".activities.ControlCenter" />
|
||||
@ -257,6 +258,22 @@
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/sleep_alarm_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<activity
|
||||
android:name=".activities.ExternalPebbleJSActivity"
|
||||
android:label="external_js"
|
||||
android:parentActivityName=".activities.AppManagerActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter" />
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="gadgetbridge" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
25
app/src/main/assets/app_config/configure.html
Normal file
25
app/src/main/assets/app_config/configure.html
Normal file
@ -0,0 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1.0, maximum-scale=1.0'>
|
||||
<script type="text/javascript" src="js/Uri.js">
|
||||
</script>
|
||||
<script type="text/javascript" src="js/gadgetbridge_boilerplate.js">
|
||||
</script>
|
||||
<script type="text/javascript">
|
||||
</script>
|
||||
<style>
|
||||
<!-- TODO -->
|
||||
</style>
|
||||
</head>
|
||||
<body style="width: 100%;">
|
||||
<div id="step1">
|
||||
<h2>Url of the configuration:</h2>
|
||||
<div id="config_url" style="height: 100px; width: 100%;"></div>
|
||||
<button name="open config" value="open config" onclick="Pebble.actuallyOpenURL()" >Open configuration website</button>
|
||||
</div>
|
||||
<div id="step2">
|
||||
<h2>Incoming configuration data:</h2>
|
||||
<div id="jsondata" style="height: 100px; width: 100%;"></div>
|
||||
<button name="send config" value="send config" onclick="Pebble.actuallySendData()" >Send data to pebble</button>
|
||||
</div>
|
||||
</body>
|
458
app/src/main/assets/app_config/js/Uri.js
Normal file
458
app/src/main/assets/app_config/js/Uri.js
Normal file
@ -0,0 +1,458 @@
|
||||
/*!
|
||||
* jsUri
|
||||
* https://github.com/derek-watson/jsUri
|
||||
*
|
||||
* Copyright 2013, Derek Watson
|
||||
* Released under the MIT license.
|
||||
*
|
||||
* Includes parseUri regular expressions
|
||||
* http://blog.stevenlevithan.com/archives/parseuri
|
||||
* Copyright 2007, Steven Levithan
|
||||
* Released under the MIT license.
|
||||
*/
|
||||
|
||||
/*globals define, module */
|
||||
|
||||
(function(global) {
|
||||
|
||||
var re = {
|
||||
starts_with_slashes: /^\/+/,
|
||||
ends_with_slashes: /\/+$/,
|
||||
pluses: /\+/g,
|
||||
query_separator: /[&;]/,
|
||||
uri_parser: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*)(?::([^:@\/]*))?)?@)?(\[[0-9a-fA-F:.]+\]|[^:\/?#]*)(?::(\d+|(?=:)))?(:)?)((((?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
|
||||
};
|
||||
|
||||
/**
|
||||
* Define forEach for older js environments
|
||||
* @see https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach#Compatibility
|
||||
*/
|
||||
if (!Array.prototype.forEach) {
|
||||
Array.prototype.forEach = function(callback, thisArg) {
|
||||
var T, k;
|
||||
|
||||
if (this == null) {
|
||||
throw new TypeError(' this is null or not defined');
|
||||
}
|
||||
|
||||
var O = Object(this);
|
||||
var len = O.length >>> 0;
|
||||
|
||||
if (typeof callback !== "function") {
|
||||
throw new TypeError(callback + ' is not a function');
|
||||
}
|
||||
|
||||
if (arguments.length > 1) {
|
||||
T = thisArg;
|
||||
}
|
||||
|
||||
k = 0;
|
||||
|
||||
while (k < len) {
|
||||
var kValue;
|
||||
if (k in O) {
|
||||
kValue = O[k];
|
||||
callback.call(T, kValue, k, O);
|
||||
}
|
||||
k++;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* unescape a query param value
|
||||
* @param {string} s encoded value
|
||||
* @return {string} decoded value
|
||||
*/
|
||||
function decode(s) {
|
||||
if (s) {
|
||||
s = s.toString().replace(re.pluses, '%20');
|
||||
s = decodeURIComponent(s);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Breaks a uri string down into its individual parts
|
||||
* @param {string} str uri
|
||||
* @return {object} parts
|
||||
*/
|
||||
function parseUri(str) {
|
||||
var parser = re.uri_parser;
|
||||
var parserKeys = ["source", "protocol", "authority", "userInfo", "user", "password", "host", "port", "isColonUri", "relative", "path", "directory", "file", "query", "anchor"];
|
||||
var m = parser.exec(str || '');
|
||||
var parts = {};
|
||||
|
||||
parserKeys.forEach(function(key, i) {
|
||||
parts[key] = m[i] || '';
|
||||
});
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Breaks a query string down into an array of key/value pairs
|
||||
* @param {string} str query
|
||||
* @return {array} array of arrays (key/value pairs)
|
||||
*/
|
||||
function parseQuery(str) {
|
||||
var i, ps, p, n, k, v, l;
|
||||
var pairs = [];
|
||||
|
||||
if (typeof(str) === 'undefined' || str === null || str === '') {
|
||||
return pairs;
|
||||
}
|
||||
|
||||
if (str.indexOf('?') === 0) {
|
||||
str = str.substring(1);
|
||||
}
|
||||
|
||||
ps = str.toString().split(re.query_separator);
|
||||
|
||||
for (i = 0, l = ps.length; i < l; i++) {
|
||||
p = ps[i];
|
||||
n = p.indexOf('=');
|
||||
|
||||
if (n !== 0) {
|
||||
k = decode(p.substring(0, n));
|
||||
v = decode(p.substring(n + 1));
|
||||
pairs.push(n === -1 ? [p, null] : [k, v]);
|
||||
}
|
||||
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Uri object
|
||||
* @constructor
|
||||
* @param {string} str
|
||||
*/
|
||||
function Uri(str) {
|
||||
this.uriParts = parseUri(str);
|
||||
this.queryPairs = parseQuery(this.uriParts.query);
|
||||
this.hasAuthorityPrefixUserPref = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define getter/setter methods
|
||||
*/
|
||||
['protocol', 'userInfo', 'host', 'port', 'path', 'anchor'].forEach(function(key) {
|
||||
Uri.prototype[key] = function(val) {
|
||||
if (typeof val !== 'undefined') {
|
||||
this.uriParts[key] = val;
|
||||
}
|
||||
return this.uriParts[key];
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* if there is no protocol, the leading // can be enabled or disabled
|
||||
* @param {Boolean} val
|
||||
* @return {Boolean}
|
||||
*/
|
||||
Uri.prototype.hasAuthorityPrefix = function(val) {
|
||||
if (typeof val !== 'undefined') {
|
||||
this.hasAuthorityPrefixUserPref = val;
|
||||
}
|
||||
|
||||
if (this.hasAuthorityPrefixUserPref === null) {
|
||||
return (this.uriParts.source.indexOf('//') !== -1);
|
||||
} else {
|
||||
return this.hasAuthorityPrefixUserPref;
|
||||
}
|
||||
};
|
||||
|
||||
Uri.prototype.isColonUri = function (val) {
|
||||
if (typeof val !== 'undefined') {
|
||||
this.uriParts.isColonUri = !!val;
|
||||
} else {
|
||||
return !!this.uriParts.isColonUri;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Serializes the internal state of the query pairs
|
||||
* @param {string} [val] set a new query string
|
||||
* @return {string} query string
|
||||
*/
|
||||
Uri.prototype.query = function(val) {
|
||||
var s = '', i, param, l;
|
||||
|
||||
if (typeof val !== 'undefined') {
|
||||
this.queryPairs = parseQuery(val);
|
||||
}
|
||||
|
||||
for (i = 0, l = this.queryPairs.length; i < l; i++) {
|
||||
param = this.queryPairs[i];
|
||||
if (s.length > 0) {
|
||||
s += '&';
|
||||
}
|
||||
if (param[1] === null) {
|
||||
s += param[0];
|
||||
} else {
|
||||
s += param[0];
|
||||
s += '=';
|
||||
if (typeof param[1] !== 'undefined') {
|
||||
s += encodeURIComponent(param[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return s.length > 0 ? '?' + s : s;
|
||||
};
|
||||
|
||||
/**
|
||||
* returns the first query param value found for the key
|
||||
* @param {string} key query key
|
||||
* @return {string} first value found for key
|
||||
*/
|
||||
Uri.prototype.getQueryParamValue = function (key) {
|
||||
var param, i, l;
|
||||
for (i = 0, l = this.queryPairs.length; i < l; i++) {
|
||||
param = this.queryPairs[i];
|
||||
if (key === param[0]) {
|
||||
return param[1];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* returns an array of query param values for the key
|
||||
* @param {string} key query key
|
||||
* @return {array} array of values
|
||||
*/
|
||||
Uri.prototype.getQueryParamValues = function (key) {
|
||||
var arr = [], i, param, l;
|
||||
for (i = 0, l = this.queryPairs.length; i < l; i++) {
|
||||
param = this.queryPairs[i];
|
||||
if (key === param[0]) {
|
||||
arr.push(param[1]);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
/**
|
||||
* removes query parameters
|
||||
* @param {string} key remove values for key
|
||||
* @param {val} [val] remove a specific value, otherwise removes all
|
||||
* @return {Uri} returns self for fluent chaining
|
||||
*/
|
||||
Uri.prototype.deleteQueryParam = function (key, val) {
|
||||
var arr = [], i, param, keyMatchesFilter, valMatchesFilter, l;
|
||||
|
||||
for (i = 0, l = this.queryPairs.length; i < l; i++) {
|
||||
|
||||
param = this.queryPairs[i];
|
||||
keyMatchesFilter = decode(param[0]) === decode(key);
|
||||
valMatchesFilter = param[1] === val;
|
||||
|
||||
if ((arguments.length === 1 && !keyMatchesFilter) || (arguments.length === 2 && (!keyMatchesFilter || !valMatchesFilter))) {
|
||||
arr.push(param);
|
||||
}
|
||||
}
|
||||
|
||||
this.queryPairs = arr;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* adds a query parameter
|
||||
* @param {string} key add values for key
|
||||
* @param {string} val value to add
|
||||
* @param {integer} [index] specific index to add the value at
|
||||
* @return {Uri} returns self for fluent chaining
|
||||
*/
|
||||
Uri.prototype.addQueryParam = function (key, val, index) {
|
||||
if (arguments.length === 3 && index !== -1) {
|
||||
index = Math.min(index, this.queryPairs.length);
|
||||
this.queryPairs.splice(index, 0, [key, val]);
|
||||
} else if (arguments.length > 0) {
|
||||
this.queryPairs.push([key, val]);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* test for the existence of a query parameter
|
||||
* @param {string} key check values for key
|
||||
* @return {Boolean} true if key exists, otherwise false
|
||||
*/
|
||||
Uri.prototype.hasQueryParam = function (key) {
|
||||
var i, len = this.queryPairs.length;
|
||||
for (i = 0; i < len; i++) {
|
||||
if (this.queryPairs[i][0] == key)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* replaces query param values
|
||||
* @param {string} key key to replace value for
|
||||
* @param {string} newVal new value
|
||||
* @param {string} [oldVal] replace only one specific value (otherwise replaces all)
|
||||
* @return {Uri} returns self for fluent chaining
|
||||
*/
|
||||
Uri.prototype.replaceQueryParam = function (key, newVal, oldVal) {
|
||||
var index = -1, len = this.queryPairs.length, i, param;
|
||||
|
||||
if (arguments.length === 3) {
|
||||
for (i = 0; i < len; i++) {
|
||||
param = this.queryPairs[i];
|
||||
if (decode(param[0]) === decode(key) && decodeURIComponent(param[1]) === decode(oldVal)) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (index >= 0) {
|
||||
this.deleteQueryParam(key, decode(oldVal)).addQueryParam(key, newVal, index);
|
||||
}
|
||||
} else {
|
||||
for (i = 0; i < len; i++) {
|
||||
param = this.queryPairs[i];
|
||||
if (decode(param[0]) === decode(key)) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.deleteQueryParam(key);
|
||||
this.addQueryParam(key, newVal, index);
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Define fluent setter methods (setProtocol, setHasAuthorityPrefix, etc)
|
||||
*/
|
||||
['protocol', 'hasAuthorityPrefix', 'isColonUri', 'userInfo', 'host', 'port', 'path', 'query', 'anchor'].forEach(function(key) {
|
||||
var method = 'set' + key.charAt(0).toUpperCase() + key.slice(1);
|
||||
Uri.prototype[method] = function(val) {
|
||||
this[key](val);
|
||||
return this;
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Scheme name, colon and doubleslash, as required
|
||||
* @return {string} http:// or possibly just //
|
||||
*/
|
||||
Uri.prototype.scheme = function() {
|
||||
var s = '';
|
||||
|
||||
if (this.protocol()) {
|
||||
s += this.protocol();
|
||||
if (this.protocol().indexOf(':') !== this.protocol().length - 1) {
|
||||
s += ':';
|
||||
}
|
||||
s += '//';
|
||||
} else {
|
||||
if (this.hasAuthorityPrefix() && this.host()) {
|
||||
s += '//';
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as Mozilla nsIURI.prePath
|
||||
* @return {string} scheme://user:password@host:port
|
||||
* @see https://developer.mozilla.org/en/nsIURI
|
||||
*/
|
||||
Uri.prototype.origin = function() {
|
||||
var s = this.scheme();
|
||||
|
||||
if (this.userInfo() && this.host()) {
|
||||
s += this.userInfo();
|
||||
if (this.userInfo().indexOf('@') !== this.userInfo().length - 1) {
|
||||
s += '@';
|
||||
}
|
||||
}
|
||||
|
||||
if (this.host()) {
|
||||
s += this.host();
|
||||
if (this.port() || (this.path() && this.path().substr(0, 1).match(/[0-9]/))) {
|
||||
s += ':' + this.port();
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a trailing slash to the path
|
||||
*/
|
||||
Uri.prototype.addTrailingSlash = function() {
|
||||
var path = this.path() || '';
|
||||
|
||||
if (path.substr(-1) !== '/') {
|
||||
this.path(path + '/');
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Serializes the internal state of the Uri object
|
||||
* @return {string}
|
||||
*/
|
||||
Uri.prototype.toString = function() {
|
||||
var path, s = this.origin();
|
||||
|
||||
if (this.isColonUri()) {
|
||||
if (this.path()) {
|
||||
s += ':'+this.path();
|
||||
}
|
||||
} else if (this.path()) {
|
||||
path = this.path();
|
||||
if (!(re.ends_with_slashes.test(s) || re.starts_with_slashes.test(path))) {
|
||||
s += '/';
|
||||
} else {
|
||||
if (s) {
|
||||
s.replace(re.ends_with_slashes, '/');
|
||||
}
|
||||
path = path.replace(re.starts_with_slashes, '/');
|
||||
}
|
||||
s += path;
|
||||
} else {
|
||||
if (this.host() && (this.query().toString() || this.anchor())) {
|
||||
s += '/';
|
||||
}
|
||||
}
|
||||
if (this.query().toString()) {
|
||||
s += this.query().toString();
|
||||
}
|
||||
|
||||
if (this.anchor()) {
|
||||
if (this.anchor().indexOf('#') !== 0) {
|
||||
s += '#';
|
||||
}
|
||||
s += this.anchor();
|
||||
}
|
||||
|
||||
return s;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clone a Uri object
|
||||
* @return {Uri} duplicate copy of the Uri
|
||||
*/
|
||||
Uri.prototype.clone = function() {
|
||||
return new Uri(this.toString());
|
||||
};
|
||||
|
||||
/**
|
||||
* export via AMD or CommonJS, otherwise leak a global
|
||||
*/
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(function() {
|
||||
return Uri;
|
||||
});
|
||||
} else if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
||||
module.exports = Uri;
|
||||
} else {
|
||||
global.Uri = Uri;
|
||||
}
|
||||
}(this));
|
128
app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js
Normal file
128
app/src/main/assets/app_config/js/gadgetbridge_boilerplate.js
Normal file
@ -0,0 +1,128 @@
|
||||
function loadScript(url, callback) {
|
||||
// Adding the script tag to the head as suggested before
|
||||
var head = document.getElementsByTagName('head')[0];
|
||||
var script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.src = url;
|
||||
|
||||
// Then bind the event to the callback function.
|
||||
// There are several events for cross browser compatibility.
|
||||
script.onreadystatechange = callback;
|
||||
script.onload = callback;
|
||||
|
||||
// Fire the loading
|
||||
head.appendChild(script);
|
||||
}
|
||||
|
||||
function getURLVariable(variable, defaultValue) {
|
||||
// Find all URL parameters
|
||||
var query = location.search.substring(1);
|
||||
var vars = query.split('&');
|
||||
for (var i = 0; i < vars.length; i++) {
|
||||
var pair = vars[i].split('=');
|
||||
|
||||
// If the query variable parameter is found, decode it to use and return it for use
|
||||
if (pair[0] === variable) {
|
||||
return decodeURIComponent(pair[1]);
|
||||
}
|
||||
}
|
||||
return defaultValue || false;
|
||||
}
|
||||
|
||||
function gbPebble() {
|
||||
this.configurationURL = null;
|
||||
this.configurationValues = null;
|
||||
|
||||
this.addEventListener = function(e, f) {
|
||||
if(e == 'ready') {
|
||||
this.ready = f;
|
||||
}
|
||||
if(e == 'showConfiguration') {
|
||||
this.showConfiguration = f;
|
||||
}
|
||||
if(e == 'webviewclosed') {
|
||||
this.parseconfig = f;
|
||||
}
|
||||
if(e == 'appmessage') {
|
||||
this.appmessage = f;
|
||||
}
|
||||
}
|
||||
|
||||
this.removeEventListener = function(e, f) {
|
||||
if(e == 'ready') {
|
||||
this.ready = null;
|
||||
}
|
||||
if(e == 'showConfiguration') {
|
||||
this.showConfiguration = null;
|
||||
}
|
||||
if(e == 'webviewclosed') {
|
||||
this.parseconfig = null;
|
||||
}
|
||||
if(e == 'appmessage') {
|
||||
this.appmessage = null;
|
||||
}
|
||||
}
|
||||
this.actuallyOpenURL = function() {
|
||||
window.open(this.configurationURL.toString(), "config");
|
||||
}
|
||||
|
||||
this.actuallySendData = function() {
|
||||
GBjs.sendAppMessage(this.configurationValues);
|
||||
}
|
||||
|
||||
//needs to be called like this because of original Pebble function name
|
||||
this.openURL = function(url) {
|
||||
document.getElementById("config_url").innerHTML=url;
|
||||
var UUID = GBjs.getAppUUID();
|
||||
this.configurationURL = new Uri(url).addQueryParam("return_to", "gadgetbridge://"+UUID+"?config=true&json=");
|
||||
}
|
||||
|
||||
this.getActiveWatchInfo = function() {
|
||||
return JSON.parse(GBjs.getActiveWatchInfo());
|
||||
}
|
||||
|
||||
this.sendAppMessage = function (dict, callbackAck, callbackNack){
|
||||
try {
|
||||
this.configurationValues = JSON.stringify(dict);
|
||||
document.getElementById("jsondata").innerHTML=this.configurationValues;
|
||||
return callbackAck;
|
||||
}
|
||||
catch (e) {
|
||||
GBjs.gbLog("sendAppMessage failed");
|
||||
return callbackNack;
|
||||
}
|
||||
}
|
||||
|
||||
this.getAccountToken = function() {
|
||||
return '';
|
||||
}
|
||||
|
||||
this.getWatchToken = function() {
|
||||
return GBjs.getWatchToken();
|
||||
}
|
||||
|
||||
this.showSimpleNotificationOnPebble = function(title, body) {
|
||||
GBjs.gbLog("app wanted to show: " + title + " body: "+ body);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
var Pebble = new gbPebble();
|
||||
|
||||
var jsConfigFile = GBjs.getAppConfigurationFile();
|
||||
if (jsConfigFile != null) {
|
||||
loadScript(jsConfigFile, function() {
|
||||
if (getURLVariable('config') == 'true') {
|
||||
document.getElementById('step1').style.display="none";
|
||||
var json_string = unescape(getURLVariable('json'));
|
||||
var t = new Object();
|
||||
t.response = json_string;
|
||||
if (json_string != '')
|
||||
Pebble.parseconfig(t);
|
||||
} else {
|
||||
document.getElementById('step2').style.display="none";
|
||||
Pebble.showConfiguration();
|
||||
}
|
||||
});
|
||||
}
|
@ -30,6 +30,7 @@ import java.util.UUID;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAppAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleProtocol;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
@ -72,6 +73,7 @@ public class AppManagerActivity extends Activity {
|
||||
private final List<GBDeviceApp> appList = new ArrayList<>();
|
||||
private GBDeviceAppAdapter mGBDeviceAppAdapter;
|
||||
private GBDeviceApp selectedApp = null;
|
||||
private GBDevice mGBDevice = null;
|
||||
|
||||
private List<GBDeviceApp> getSystemApps() {
|
||||
List<GBDeviceApp> systemApps = new ArrayList<>();
|
||||
@ -116,6 +118,13 @@ public class AppManagerActivity extends Activity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle extras = getIntent().getExtras();
|
||||
if (extras != null) {
|
||||
mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Must provide a device when invoking this activity");
|
||||
}
|
||||
|
||||
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||
|
||||
setContentView(R.layout.activity_appmanager);
|
||||
@ -189,6 +198,14 @@ public class AppManagerActivity extends Activity {
|
||||
case R.id.appmanager_health_activate:
|
||||
GBApplication.deviceService().onInstallApp(Uri.parse("fake://health"));
|
||||
return true;
|
||||
case R.id.appmanager_app_configure:
|
||||
GBApplication.deviceService().onAppStart(selectedApp.getUUID(), true);
|
||||
|
||||
Intent startIntent = new Intent(getApplicationContext(), ExternalPebbleJSActivity.class);
|
||||
startIntent.putExtra("app_uuid", selectedApp.getUUID());
|
||||
startIntent.putExtra(GBDevice.EXTRA_DEVICE, mGBDevice);
|
||||
startActivity(startIntent);
|
||||
return true;
|
||||
default:
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
|
@ -131,7 +131,7 @@ public class ControlCenter extends Activity {
|
||||
@Override
|
||||
public void onItemClick(AdapterView parent, View v, int position, long id) {
|
||||
GBDevice gbDevice = deviceList.get(position);
|
||||
if (gbDevice.isConnected()) {
|
||||
if (gbDevice.isInitialized()) {
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
|
||||
Class<? extends Activity> primaryActivity = coordinator.getPrimaryActivity();
|
||||
if (primaryActivity != null) {
|
||||
|
@ -0,0 +1,183 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.util.Log;
|
||||
import android.view.MenuItem;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
|
||||
|
||||
public class ExternalPebbleJSActivity extends Activity {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ExternalPebbleJSActivity.class);
|
||||
|
||||
private UUID appUuid;
|
||||
private GBDevice mGBDevice = null;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
Bundle extras = getIntent().getExtras();
|
||||
if (extras != null) {
|
||||
mGBDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Must provide a device when invoking this activity");
|
||||
}
|
||||
|
||||
String queryString = "";
|
||||
Uri uri = getIntent().getData();
|
||||
if (uri != null) {
|
||||
//getting back with configuration data
|
||||
appUuid = UUID.fromString(uri.getHost());
|
||||
queryString = uri.getEncodedQuery();
|
||||
} else {
|
||||
appUuid = (UUID) getIntent().getSerializableExtra("app_uuid");
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_external_pebble_js);
|
||||
getActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
WebView myWebView = (WebView) findViewById(R.id.configureWebview);
|
||||
myWebView.clearCache(true);
|
||||
WebSettings webSettings = myWebView.getSettings();
|
||||
webSettings.setJavaScriptEnabled(true);
|
||||
//needed to access the DOM
|
||||
webSettings.setDomStorageEnabled(true);
|
||||
|
||||
JSInterface gbJSInterface = new JSInterface();
|
||||
myWebView.addJavascriptInterface(gbJSInterface, "GBjs");
|
||||
|
||||
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 JSInterface {
|
||||
|
||||
public JSInterface() {
|
||||
}
|
||||
|
||||
@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 cur_key;
|
||||
for (Iterator<String> key = in.keys(); key.hasNext(); ) {
|
||||
cur_key = key.next();
|
||||
int pebbleAppIndex = knownKeys.optInt(cur_key);
|
||||
if (pebbleAppIndex != 0) {
|
||||
Object obj = in.get(cur_key);
|
||||
if (obj instanceof Boolean) {
|
||||
obj = ((Boolean) obj) ? "true" : "false";
|
||||
}
|
||||
out.put(String.valueOf(pebbleAppIndex), obj);
|
||||
} else {
|
||||
GB.toast("Discarded key " + cur_key + ", not found in the local configuration.", 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.getHardwareVersion()));
|
||||
wi.put("model", PebbleUtils.getModel(mGBDevice.getHardwareVersion()));
|
||||
//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 getAppUUID() {
|
||||
return appUuid.toString();
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String getWatchToken() {
|
||||
//specification says: A string that is 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();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
NavUtils.navigateUpFromSameTask(this);
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -36,6 +36,8 @@ public interface EventHandler {
|
||||
|
||||
void onAppDelete(UUID uuid);
|
||||
|
||||
void onAppConfiguration(UUID appUuid, String config);
|
||||
|
||||
void onFetchActivityData();
|
||||
|
||||
void onReboot();
|
||||
@ -45,4 +47,5 @@ public interface EventHandler {
|
||||
void onFindDevice(boolean start);
|
||||
|
||||
void onScreenshotReq();
|
||||
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
|
||||
|
||||
public class PBWInstallHandler implements InstallHandler {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PBWInstallHandler.class);
|
||||
@ -49,15 +50,7 @@ public class PBWInstallHandler implements InstallHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
String hwRev = device.getHardwareVersion();
|
||||
String platformName;
|
||||
if (hwRev.startsWith("snowy")) {
|
||||
platformName = "basalt";
|
||||
} else if (hwRev.startsWith("spalding")) {
|
||||
platformName = "chalk";
|
||||
} else {
|
||||
platformName = "aplite";
|
||||
}
|
||||
String platformName = PebbleUtils.getPlatformName(device.getHardwareVersion());
|
||||
|
||||
try {
|
||||
mPBWReader = new PBWReader(mUri, mContext, platformName);
|
||||
@ -173,6 +166,24 @@ public class PBWInstallHandler implements InstallHandler {
|
||||
} catch (JSONException e) {
|
||||
LOG.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
String jsConfigFile = mPBWReader.getJsConfigurationFile();
|
||||
|
||||
if (jsConfigFile != null) {
|
||||
outputFile = new File(destDir, app.getUUID().toString() + "_config.js");
|
||||
try {
|
||||
writer = new BufferedWriter(new FileWriter(outputFile));
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to open output file: " + e.getMessage(), e);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
writer.write(jsConfigFile);
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
LOG.error("Failed to write to output file: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
|
@ -57,6 +57,7 @@ public class PBWReader {
|
||||
private short mAppVersion;
|
||||
private int mIconId;
|
||||
private int mFlags;
|
||||
private String jsConfigurationFile = null;
|
||||
|
||||
private JSONObject mAppKeys = null;
|
||||
|
||||
@ -212,6 +213,20 @@ public class PBWReader {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
} else if (fileName.equals("pebble-js-app.js")) {
|
||||
LOG.info("Found JS file: app supports configuration.");
|
||||
long bytes = ze.getSize();
|
||||
if (bytes > 65536) {
|
||||
LOG.info("size exceeding 64k, skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
while ((count = zis.read(buffer)) != -1) {
|
||||
baos.write(buffer, 0, count);
|
||||
}
|
||||
|
||||
jsConfigurationFile = baos.toString();
|
||||
} else if (fileName.equals(platformDir + "pebble-app.bin")) {
|
||||
zis.read(buffer, 0, 108);
|
||||
byte[] tmp_buf = new byte[32];
|
||||
@ -327,4 +342,8 @@ public class PBWReader {
|
||||
public JSONObject getAppKeysJSON() {
|
||||
return mAppKeys;
|
||||
}
|
||||
|
||||
public String getJsConfigurationFile() {
|
||||
return jsConfigurationFile;
|
||||
}
|
||||
}
|
@ -159,6 +159,14 @@ public class GBDeviceService implements DeviceService {
|
||||
invokeService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppConfiguration(UUID uuid, String config) {
|
||||
Intent intent = createIntent().setAction(ACTION_APP_CONFIGURE)
|
||||
.putExtra(EXTRA_APP_UUID, uuid)
|
||||
.putExtra(EXTRA_APP_CONFIG, config);
|
||||
invokeService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchActivityData() {
|
||||
Intent intent = createIntent().setAction(ACTION_FETCH_ACTIVITY_DATA);
|
||||
|
@ -15,7 +15,6 @@ public interface DeviceService extends EventHandler {
|
||||
String ACTION_START = PREFIX + ".action.start";
|
||||
String ACTION_CONNECT = PREFIX + ".action.connect";
|
||||
String ACTION_NOTIFICATION = PREFIX + ".action.notification";
|
||||
String ACTION_NOTIFICATION_SMS = PREFIX + ".action.notification_sms";
|
||||
String ACTION_CALLSTATE = PREFIX + ".action.callstate";
|
||||
String ACTION_SETTIME = PREFIX + ".action.settime";
|
||||
String ACTION_SETMUSICINFO = PREFIX + ".action.setmusicinfo";
|
||||
@ -24,6 +23,7 @@ public interface DeviceService extends EventHandler {
|
||||
String ACTION_REQUEST_SCREENSHOT = PREFIX + ".action.request_screenshot";
|
||||
String ACTION_STARTAPP = PREFIX + ".action.startapp";
|
||||
String ACTION_DELETEAPP = PREFIX + ".action.deleteapp";
|
||||
String ACTION_APP_CONFIGURE = PREFIX + ".action.app_configure";
|
||||
String ACTION_INSTALL = PREFIX + ".action.install";
|
||||
String ACTION_REBOOT = PREFIX + ".action.reboot";
|
||||
String ACTION_HEARTRATE_TEST = PREFIX + ".action.heartrate_test";
|
||||
@ -51,6 +51,7 @@ public interface DeviceService extends EventHandler {
|
||||
String EXTRA_MUSIC_TRACK = "music_track";
|
||||
String EXTRA_APP_UUID = "app_uuid";
|
||||
String EXTRA_APP_START = "app_start";
|
||||
String EXTRA_APP_CONFIG = "app_config";
|
||||
String EXTRA_URI = "uri";
|
||||
String EXTRA_ALARMS = "alarms";
|
||||
String EXTRA_PERFORM_PAIR = "perform_pair";
|
||||
|
@ -40,6 +40,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_APP_CONFIGURE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CALLSTATE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETEAPP;
|
||||
@ -60,6 +61,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SE
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_STARTAPP;
|
||||
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_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_UUID;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_COMMAND;
|
||||
@ -306,6 +308,11 @@ public class DeviceCommunicationService extends Service {
|
||||
mDeviceSupport.onAppDelete(uuid);
|
||||
break;
|
||||
}
|
||||
case ACTION_APP_CONFIGURE: {
|
||||
UUID uuid = (UUID) intent.getSerializableExtra(EXTRA_APP_UUID);
|
||||
String config = intent.getStringExtra(EXTRA_APP_CONFIG);
|
||||
mDeviceSupport.onAppConfiguration(uuid, config);
|
||||
}
|
||||
case ACTION_INSTALL:
|
||||
Uri uri = intent.getParcelableExtra(EXTRA_URI);
|
||||
if (uri != null) {
|
||||
|
@ -178,6 +178,14 @@ public class ServiceDeviceSupport implements DeviceSupport {
|
||||
delegate.onAppDelete(uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppConfiguration(UUID uuid, String config) {
|
||||
if (checkBusy("app configuration")) {
|
||||
return;
|
||||
}
|
||||
delegate.onAppConfiguration(uuid, config);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchActivityData() {
|
||||
if (checkBusy("fetch activity data")) {
|
||||
|
@ -656,6 +656,11 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
// not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppConfiguration(UUID uuid, String config) {
|
||||
// not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScreenshotReq() {
|
||||
// not supported
|
||||
|
@ -96,18 +96,24 @@ public class AppMessageHandlerPebStyle extends AppMessageHandler {
|
||||
|
||||
@Override
|
||||
public GBDeviceEvent[] handleMessage(ArrayList<Pair<Integer, Object>> pairs) {
|
||||
return null;
|
||||
/*
|
||||
GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
|
||||
ByteBuffer buf = ByteBuffer.allocate(encodeAck().length + encodePebStyleConfig().length);
|
||||
buf.put(encodeAck());
|
||||
buf.put(encodePebStyleConfig());
|
||||
sendBytes.encodedBytes = buf.array();
|
||||
return new GBDeviceEvent[]{sendBytes};
|
||||
*/
|
||||
}
|
||||
|
||||
@Override
|
||||
public GBDeviceEvent[] pushMessage() {
|
||||
return null;
|
||||
/*
|
||||
GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
|
||||
sendBytes.encodedBytes = encodePebStyleConfig();
|
||||
return new GBDeviceEvent[]{sendBytes};
|
||||
*/
|
||||
}
|
||||
}
|
@ -100,8 +100,11 @@ public class AppMessageHandlerTimeStylePebble extends AppMessageHandler {
|
||||
|
||||
@Override
|
||||
public GBDeviceEvent[] handleMessage(ArrayList<Pair<Integer, Object>> pairs) {
|
||||
return null;
|
||||
/*
|
||||
GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
|
||||
sendBytes.encodedBytes = encodeTimeStylePebbleConfig();
|
||||
return new GBDeviceEvent[]{sendBytes};
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
|
||||
|
||||
public class PebbleIoThread extends GBDeviceIoThread {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PebbleIoThread.class);
|
||||
@ -577,15 +578,7 @@ public class PebbleIoThread extends GBDeviceIoThread {
|
||||
return;
|
||||
}
|
||||
|
||||
String hwRev = gbDevice.getHardwareVersion();
|
||||
String platformName;
|
||||
if (hwRev.startsWith("snowy")) {
|
||||
platformName = "basalt";
|
||||
} else if (hwRev.startsWith("spalding")) {
|
||||
platformName = "chalk";
|
||||
} else {
|
||||
platformName = "aplite";
|
||||
}
|
||||
String platformName = PebbleUtils.getPlatformName(gbDevice.getHardwareVersion());
|
||||
|
||||
try {
|
||||
mPBWReader = new PBWReader(uri, getContext(), platformName);
|
||||
|
@ -1,8 +1,14 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
|
||||
@ -37,6 +43,24 @@ public class PebbleSupport extends AbstractSerialDeviceSupport {
|
||||
getDeviceIOThread().installApp(uri, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppConfiguration(UUID uuid, String config) {
|
||||
try {
|
||||
ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>();
|
||||
|
||||
JSONObject json = new JSONObject(config);
|
||||
Iterator<String> keysIterator = json.keys();
|
||||
while (keysIterator.hasNext()) {
|
||||
String keyStr = keysIterator.next();
|
||||
Object object = json.get(keyStr);
|
||||
pairs.add(new Pair<>(Integer.parseInt(keyStr), object));
|
||||
}
|
||||
getDeviceIOThread().write(((PebbleProtocol) getDeviceProtocol()).encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, uuid, pairs));
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeartRateTest() {
|
||||
|
||||
|
@ -0,0 +1,27 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
|
||||
public class PebbleUtils {
|
||||
public static String getPlatformName(String hwRev) {
|
||||
String platformName;
|
||||
if (hwRev.startsWith("snowy")) {
|
||||
platformName = "basalt";
|
||||
} else if (hwRev.startsWith("spalding")) {
|
||||
platformName = "chalk";
|
||||
} else {
|
||||
platformName = "aplite";
|
||||
}
|
||||
return platformName;
|
||||
}
|
||||
public static String getModel(String hwRev) {
|
||||
//TODO: get real data?
|
||||
String model;
|
||||
if (hwRev.startsWith("snowy")) {
|
||||
model = "pebble_time_black";
|
||||
} else if (hwRev.startsWith("spalding")) {
|
||||
model = "pebble_time_round_black_20mm";
|
||||
} else {
|
||||
model = "pebble_black";
|
||||
}
|
||||
return model;
|
||||
}
|
||||
}
|
5
app/src/main/res/layout/activity_external_pebble_js.xml
Normal file
5
app/src/main/res/layout/activity_external_pebble_js.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/configureWebview"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent" />
|
@ -12,5 +12,7 @@
|
||||
<item
|
||||
android:id="@+id/appmanager_health_deactivate"
|
||||
android:title="@string/appmanager_health_deactivate"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/appmanager_app_configure"
|
||||
android:title="@string/app_configure"/>
|
||||
</menu>
|
@ -224,6 +224,7 @@
|
||||
<string name="appmanager_health_deactivate">Deactivate</string>
|
||||
<string name="authenticating">authenticating</string>
|
||||
<string name="authentication_required">authentication required</string>
|
||||
<string name="app_configure">Configure</string>
|
||||
|
||||
<string name="appwidget_text">Zzz</string>
|
||||
<string name="add_widget">Add widget</string>
|
||||
|
@ -90,6 +90,11 @@ public class TestDeviceSupport extends AbstractDeviceSupport {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppConfiguration(UUID appUuid, String config) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFetchActivityData() {
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user