mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Merge branch 'master' into bip-wip
This commit is contained in:
@@ -1,5 +1,16 @@
|
||||
### Changelog
|
||||
|
||||
#### Version 0.24.0
|
||||
* Fix logs sometimes not containing stacktraces
|
||||
* Support periodic database export
|
||||
* Support transliteration for Arabic and Farsi
|
||||
* Try to make alarm details scrollable (for small devices)
|
||||
* Amazfit Bip: Implement find phone feature
|
||||
* Amazfit Bip: Support flashing latest GPS firmware
|
||||
* Amazfit Cor: Support flashing latest firmware
|
||||
* Pebble: Fix crash with experimental background javascript
|
||||
* Charts: Several fixes to the MPAndroidChart library
|
||||
|
||||
#### Version 0.23.2
|
||||
* Mi Band 1S: Fix sync problem with firmware 4.16.11.15 (probably also Mi Band 1.0.15.0 and Mi Band 1A 5.16.11.15)
|
||||
* Amazfit Cor: Fix problem with firmware >=1.0.6.27 being detected as Mi Band 2
|
||||
|
||||
+2
-2
@@ -26,8 +26,8 @@ android {
|
||||
targetSdkVersion 25
|
||||
|
||||
// note: always bump BOTH versionCode and versionName!
|
||||
versionName "0.23.2"
|
||||
versionCode 116
|
||||
versionName "0.24.0"
|
||||
versionCode 117
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
buildTypes {
|
||||
|
||||
@@ -349,6 +349,12 @@
|
||||
<action android:name="nodomain.freeyourgadget.gadgetbridge.callcontrol" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:enabled="true"
|
||||
android:name="nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter"
|
||||
android:exported="false">
|
||||
|
||||
</receiver>
|
||||
|
||||
<!--
|
||||
forcing the DebugActivity to portrait mode avoids crashes with the progress
|
||||
@@ -396,6 +402,9 @@
|
||||
android:name=".activities.VibrationActivity"
|
||||
android:label="@string/title_activity_vibration"
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
android:name=".activities.FindPhoneActivity"
|
||||
android:label="Find Phone" />
|
||||
|
||||
<provider
|
||||
android:name=".contentprovider.PebbleContentProvider"
|
||||
|
||||
+87
-71
@@ -17,10 +17,10 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@@ -34,6 +34,7 @@ import android.widget.Toast;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@@ -51,6 +52,10 @@ public class ExternalPebbleJSActivity extends AbstractGBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ExternalPebbleJSActivity.class);
|
||||
|
||||
private Uri confUri;
|
||||
/**
|
||||
* When bgjs is enabled, this field refers to the WebViewSingleton,
|
||||
* otherwise it refers to the legacy webview from the activity_legacy_external_pebble_js layout
|
||||
*/
|
||||
private WebView myWebView;
|
||||
public static final String START_BG_WEBVIEW = "start_webview";
|
||||
public static final String SHOW_CONFIG = "configure";
|
||||
@@ -58,72 +63,90 @@ public class ExternalPebbleJSActivity extends AbstractGBActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
GBDevice currentDevice;
|
||||
UUID currentUUID;
|
||||
Bundle extras = getIntent().getExtras();
|
||||
if (extras != null) {
|
||||
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.ensureCreated(this);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Must provide a device when invoking this activity");
|
||||
if (extras == null) {
|
||||
throw new IllegalArgumentException("Must provide device and uuid in extras when invoking this activity");
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
|
||||
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");
|
||||
|
||||
if (extras.getBoolean(START_BG_WEBVIEW, false)) {
|
||||
startBackgroundWebViewAndFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
GBDevice currentDevice = extras.getParcelable(GBDevice.EXTRA_DEVICE);
|
||||
UUID currentUUID = (UUID) extras.getSerializable(DeviceService.EXTRA_APP_UUID);
|
||||
|
||||
if (GBApplication.getGBPrefs().isBackgroundJsEnabled()) {
|
||||
if (extras.getBoolean(SHOW_CONFIG, false)) {
|
||||
Objects.requireNonNull(currentDevice, "Must provide a device when invoking this activity");
|
||||
Objects.requireNonNull(currentUUID, "Must provide a uuid when invoking this activity");
|
||||
|
||||
WebViewSingleton.runJavascriptInterface(currentDevice, currentUUID);
|
||||
}
|
||||
|
||||
// FIXME: is this really supposed to be outside the check for SHOW_CONFIG?
|
||||
setupBGWebView();
|
||||
} else {
|
||||
Objects.requireNonNull(currentDevice, "Must provide a device when invoking this activity without bgjs");
|
||||
Objects.requireNonNull(currentUUID, "Must provide a uuid when invoking this activity without bgjs");
|
||||
setupLegacyWebView(currentDevice, currentUUID);
|
||||
}
|
||||
}
|
||||
|
||||
private void startBackgroundWebViewAndFinish() {
|
||||
if (GBApplication.getGBPrefs().isBackgroundJsEnabled()) {
|
||||
WebViewSingleton.ensureCreated(this);
|
||||
} else {
|
||||
LOG.warn("BGJs disabled, not starting webview");
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
private void setupBGWebView() {
|
||||
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(), "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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(View v) {
|
||||
v.removeOnAttachStateChangeListener(this);
|
||||
FrameLayout fl = (FrameLayout) findViewById(R.id.webview_placeholder);
|
||||
fl.removeAllViews();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupLegacyWebView(@NonNull GBDevice device, @NonNull UUID uuid) {
|
||||
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(device, uuid);
|
||||
myWebView.addJavascriptInterface(gbJSInterface, "GBjs");
|
||||
myWebView.addJavascriptInterface(new ActivityJSInterface(), "GBActivity");
|
||||
|
||||
myWebView.loadUrl("file:///android_asset/app_config/configure.html");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -160,16 +183,9 @@ public class ExternalPebbleJSActivity extends AbstractGBActivity {
|
||||
|
||||
private class ActivityJSInterface {
|
||||
|
||||
Context mContext;
|
||||
|
||||
ActivityJSInterface(Context c) {
|
||||
mContext = c;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public void closeActivity() {
|
||||
NavUtils.navigateUpFromSameTask((ExternalPebbleJSActivity) mContext);
|
||||
NavUtils.navigateUpFromSameTask(ExternalPebbleJSActivity.this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
/* Copyright (C) 2017 Andreas Shimokawa
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.media.RingtoneManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.RemoteInput;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
|
||||
public class FindPhoneActivity extends AbstractGBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FindPhoneActivity.class);
|
||||
|
||||
public static final String ACTION_FOUND
|
||||
= "nodomain.freeyourgadget.gadgetbridge.findphone.action.reply";
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action != null) {
|
||||
switch (action) {
|
||||
case ACTION_FOUND: {
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AudioManager mAudioManager;
|
||||
int userVolume;
|
||||
MediaPlayer mp;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_find_phone);
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(ACTION_FOUND);
|
||||
filter.addAction(DeviceService.ACTION_HEARTRATE_MEASUREMENT);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
|
||||
registerReceiver(mReceiver, filter); // for ACTION_FOUND
|
||||
|
||||
Button foundButton = (Button) findViewById(R.id.foundbutton);
|
||||
foundButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
playRingtone();
|
||||
}
|
||||
|
||||
public void playRingtone(){
|
||||
mAudioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
|
||||
if (mAudioManager != null) {
|
||||
userVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_ALARM);
|
||||
}
|
||||
mp = new MediaPlayer();
|
||||
|
||||
Uri ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE);
|
||||
|
||||
try {
|
||||
mp.setDataSource(this, ringtoneUri);
|
||||
mp.setAudioStreamType(AudioManager.STREAM_ALARM);
|
||||
mp.setLooping(true);
|
||||
mp.prepare();
|
||||
mp.start();
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, mAudioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), AudioManager.FLAG_PLAY_SOUND);
|
||||
|
||||
}
|
||||
|
||||
public void stopSound() {
|
||||
mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, userVolume, AudioManager.FLAG_PLAY_SOUND);
|
||||
mp.stop();
|
||||
mp.reset();
|
||||
mp.release();
|
||||
}
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
stopSound();
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
unregisterReceiver(mReceiver);
|
||||
}
|
||||
}
|
||||
+111
-8
@@ -18,19 +18,27 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.database.Cursor;
|
||||
import android.location.Criteria;
|
||||
import android.location.Location;
|
||||
import android.location.LocationListener;
|
||||
import android.location.LocationManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.preference.EditTextPreference;
|
||||
import android.preference.ListPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceCategory;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.widget.Toast;
|
||||
@@ -39,6 +47,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
@@ -46,11 +55,14 @@ import java.util.Locale;
|
||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.ActivityUser.PREF_USER_HEIGHT_CM;
|
||||
@@ -64,6 +76,8 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
|
||||
public static final String PREF_MEASUREMENT_SYSTEM = "measurement_system";
|
||||
|
||||
private static final int FILE_REQUEST_CODE = 4711;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
@@ -262,17 +276,58 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
|
||||
pref = findPreference("weather_city");
|
||||
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
// reset city id and force a new lookup
|
||||
GBApplication.getPrefs().getPreferences().edit().putString("weather_cityid",null).apply();
|
||||
preference.setSummary(newVal.toString());
|
||||
Intent intent = new Intent("GB_UPDATE_WEATHER");
|
||||
intent.setPackage(BuildConfig.APPLICATION_ID);
|
||||
sendBroadcast(intent);
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
// reset city id and force a new lookup
|
||||
GBApplication.getPrefs().getPreferences().edit().putString("weather_cityid", null).apply();
|
||||
preference.setSummary(newVal.toString());
|
||||
Intent intent = new Intent("GB_UPDATE_WEATHER");
|
||||
intent.setPackage(BuildConfig.APPLICATION_ID);
|
||||
sendBroadcast(intent);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
pref = findPreference(GBPrefs.AUTO_EXPORT_LOCATION);
|
||||
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent i = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
||||
i.setType("application/x-sqlite3");
|
||||
i.addCategory(Intent.CATEGORY_OPENABLE);
|
||||
i.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
String title = getApplicationContext().getString(R.string.choose_auto_export_location);
|
||||
startActivityForResult(Intent.createChooser(i, title), FILE_REQUEST_CODE);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
pref.setSummary(getAutoExportLocationSummary());
|
||||
|
||||
pref = findPreference(GBPrefs.AUTO_EXPORT_INTERVAL);
|
||||
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object autoExportInterval) {
|
||||
String summary = String.format(
|
||||
getApplicationContext().getString(R.string.pref_summary_auto_export_interval),
|
||||
(int) autoExportInterval);
|
||||
preference.setSummary(summary);
|
||||
boolean auto_export_enabled = GBApplication.getPrefs().getBoolean(GBPrefs.AUTO_EXPORT_ENABLED, false);
|
||||
PeriodicExporter.sheduleAlarm(getApplicationContext(), (int) autoExportInterval, auto_export_enabled);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
int autoExportInterval = GBApplication.getPrefs().getInt(GBPrefs.AUTO_EXPORT_INTERVAL, 0);
|
||||
String summary = String.format(
|
||||
getApplicationContext().getString(R.string.pref_summary_auto_export_interval),
|
||||
(int) autoExportInterval);
|
||||
pref.setSummary(summary);
|
||||
|
||||
findPreference(GBPrefs.AUTO_EXPORT_ENABLED).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object autoExportEnabled) {
|
||||
int autoExportInterval = GBApplication.getPrefs().getInt(GBPrefs.AUTO_EXPORT_INTERVAL, 0);
|
||||
PeriodicExporter.sheduleAlarm(getApplicationContext(), autoExportInterval, (boolean) autoExportEnabled);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
// Get all receivers of Media Buttons
|
||||
@@ -301,6 +356,54 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
audioPlayer.setDefaultValue(newValues[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
||||
if (requestCode == FILE_REQUEST_CODE && intent != null) {
|
||||
Uri uri = intent.getData();
|
||||
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
PreferenceManager
|
||||
.getDefaultSharedPreferences(this)
|
||||
.edit()
|
||||
.putString(GBPrefs.AUTO_EXPORT_LOCATION, uri.toString())
|
||||
.apply();
|
||||
String summary = getAutoExportLocationSummary();
|
||||
findPreference(GBPrefs.AUTO_EXPORT_LOCATION).setSummary(summary);
|
||||
boolean autoExportEnabled = GBApplication
|
||||
.getPrefs().getBoolean(GBPrefs.AUTO_EXPORT_ENABLED, false);
|
||||
int autoExportPeriod = GBApplication
|
||||
.getPrefs().getInt(GBPrefs.AUTO_EXPORT_INTERVAL, 0);
|
||||
PeriodicExporter.sheduleAlarm(getApplicationContext(), autoExportPeriod, autoExportEnabled);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Either returns the file path of the selected document, or the display name
|
||||
*/
|
||||
private String getAutoExportLocationSummary() {
|
||||
String autoExportLocation = GBApplication.getPrefs().getString(GBPrefs.AUTO_EXPORT_LOCATION, null);
|
||||
if (autoExportLocation == null) {
|
||||
return "";
|
||||
}
|
||||
Uri uri = Uri.parse(autoExportLocation);
|
||||
try {
|
||||
String filePath = AndroidUtils.getFilePath(getApplicationContext(), uri);
|
||||
if (filePath != null) {
|
||||
return filePath;
|
||||
}
|
||||
} catch (URISyntaxException e) {
|
||||
return "";
|
||||
}
|
||||
Cursor cursor = getContentResolver().query(
|
||||
uri,
|
||||
new String[] { DocumentsContract.Document.COLUMN_DISPLAY_NAME },
|
||||
null, null, null, null
|
||||
);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/*
|
||||
* delayed execution so that the preferences are applied first
|
||||
*/
|
||||
|
||||
+6
-6
@@ -147,7 +147,7 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
||||
List<GBDeviceApp> cachedAppList = new ArrayList<>();
|
||||
File cachePath;
|
||||
try {
|
||||
cachePath = new File(FileUtils.getExternalFilesDir().getPath() + "/pbw-cache");
|
||||
cachePath = PebbleUtils.getPbwCacheDir();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("could not get external dir while reading pbw cache.");
|
||||
return cachedAppList;
|
||||
@@ -362,18 +362,18 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
||||
private boolean onContextItemSelected(MenuItem item, GBDeviceApp selectedApp) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.appmanager_app_delete_cache:
|
||||
String baseName;
|
||||
File pbwCacheDir;
|
||||
try {
|
||||
baseName = FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID();
|
||||
pbwCacheDir = PebbleUtils.getPbwCacheDir();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("could not get external dir while trying to access pbw cache.");
|
||||
return true;
|
||||
}
|
||||
|
||||
String baseName = selectedApp.getUUID().toString();
|
||||
String[] suffixToDelete = new String[]{".pbw", ".json", "_config.js", "_preset.json"};
|
||||
|
||||
for (String suffix : suffixToDelete) {
|
||||
File fileToDelete = new File(baseName + suffix);
|
||||
File fileToDelete = new File(pbwCacheDir,baseName + suffix);
|
||||
if (!fileToDelete.delete()) {
|
||||
LOG.warn("could not delete file from pbw cache: " + fileToDelete.toString());
|
||||
} else {
|
||||
@@ -394,7 +394,7 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
||||
case R.id.appmanager_app_reinstall:
|
||||
File cachePath;
|
||||
try {
|
||||
cachePath = new File(FileUtils.getExternalFilesDir().getPath() + "/pbw-cache/" + selectedApp.getUUID() + ".pbw");
|
||||
cachePath = new File(PebbleUtils.getPbwCacheDir(), selectedApp.getUUID() + ".pbw");
|
||||
} catch (IOException e) {
|
||||
LOG.warn("could not get external dir while trying to access pbw cache.");
|
||||
return true;
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
@@ -29,6 +30,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
@@ -118,6 +120,16 @@ public class DBHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public void exportDB(DBHandler dbHandler, OutputStream dest) throws IOException {
|
||||
String dbPath = getClosedDBPath(dbHandler);
|
||||
try {
|
||||
File source = new File(dbPath);
|
||||
FileUtils.copyFileToStream(source, dest);
|
||||
} finally {
|
||||
dbHandler.openDb();
|
||||
}
|
||||
}
|
||||
|
||||
private String getDate() {
|
||||
return new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US).format(new Date());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.database;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
/**
|
||||
* Created by maufl on 1/4/18.
|
||||
*/
|
||||
|
||||
public class PeriodicExporter extends BroadcastReceiver {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PeriodicExporter.class);
|
||||
|
||||
public static void enablePeriodicExport(Context context) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
boolean autoExportEnabled = prefs.getBoolean(GBPrefs.AUTO_EXPORT_ENABLED, false);
|
||||
Integer autoExportInterval = prefs.getInt(GBPrefs.AUTO_EXPORT_INTERVAL, 0);
|
||||
sheduleAlarm(context, autoExportInterval, autoExportEnabled);
|
||||
}
|
||||
|
||||
public static void sheduleAlarm(Context context, Integer autoExportInterval, boolean autoExportEnabled) {
|
||||
Intent i = new Intent(context, PeriodicExporter.class);
|
||||
PendingIntent pi = PendingIntent.getBroadcast(context, 0 , i, 0);
|
||||
AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||
am.cancel(pi);
|
||||
if (!autoExportEnabled) {
|
||||
return;
|
||||
}
|
||||
int exportPeriod = autoExportInterval * 60 * 60 * 1000;
|
||||
if (exportPeriod == 0) {
|
||||
return;
|
||||
}
|
||||
LOG.info("Enabling periodic export");
|
||||
am.setInexactRepeating(
|
||||
AlarmManager.ELAPSED_REALTIME,
|
||||
SystemClock.elapsedRealtime() + exportPeriod,
|
||||
exportPeriod,
|
||||
pi
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
LOG.info("Exporting DB");
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
DBHelper helper = new DBHelper(context);
|
||||
String dst = GBApplication.getPrefs().getString(GBPrefs.AUTO_EXPORT_LOCATION, null);
|
||||
if (dst == null) {
|
||||
LOG.info("Unable to export DB, export location not set");
|
||||
return;
|
||||
}
|
||||
Uri dstUri = Uri.parse(dst);
|
||||
OutputStream out = context.getContentResolver().openOutputStream(dstUri);
|
||||
helper.exportDB(dbHandler, out);
|
||||
} catch (Exception ex) {
|
||||
GB.updateExportFailedNotification(context.getString(R.string.notif_export_failed_title), context);
|
||||
LOG.info("Exception while exporting DB: ", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -27,4 +27,14 @@ public class GBDeviceEventAppMessage extends GBDeviceEvent {
|
||||
public UUID appUUID;
|
||||
public int id;
|
||||
public String message;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GBDeviceEventAppMessage{" +
|
||||
"type=" + type +
|
||||
", appUUID=" + appUUID +
|
||||
", message='" + message + '\'' +
|
||||
", id=" + id +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
|
||||
|
||||
public class GBDeviceEventFindPhone extends GBDeviceEvent {
|
||||
public Event event = Event.UNKNOWN;
|
||||
|
||||
public enum Event {
|
||||
UNKNOWN,
|
||||
START,
|
||||
STOP
|
||||
}
|
||||
}
|
||||
+2
@@ -35,4 +35,6 @@ public class AmazfitBipService {
|
||||
|
||||
public static final byte COMMAND_ACTIVITY_DATA_TYPE_SPORTS_SUMMARIES = 0x05;
|
||||
public static final byte COMMAND_ACTIVITY_DATA_TYPE_SPORTS_DETAILS = 0x06;
|
||||
|
||||
public static final byte[] COMMAND_ACK_FIND_PHONE_IN_PROGRESS = new byte[]{ENDPOINT_DISPLAY, 0x14, 0x00, 0x00};
|
||||
}
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ public class MiBand2HRXCoordinator extends HuamiCoordinator {
|
||||
try {
|
||||
BluetoothDevice device = candidate.getDevice();
|
||||
String name = device.getName();
|
||||
if (name != null && name.equalsIgnoreCase("Mi Band HRX")) {
|
||||
if (name != null && (name.equalsIgnoreCase("Mi Band HRX") || name.equalsIgnoreCase("Mi Band 2i"))) {
|
||||
return DeviceType.MIBAND2;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
|
||||
+1
-1
@@ -154,7 +154,7 @@ public class PBWInstallHandler implements InstallHandler {
|
||||
File destDir;
|
||||
GBDeviceApp app = mPBWReader.getGBDeviceApp();
|
||||
try {
|
||||
destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache");
|
||||
destDir = PebbleUtils.getPbwCacheDir();
|
||||
destDir.mkdirs();
|
||||
FileUtils.copyURItoFile(mContext, mUri, new File(destDir, app.getUUID().toString() + ".pbw"));
|
||||
|
||||
|
||||
+2
@@ -22,6 +22,7 @@ import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
|
||||
|
||||
public class AutoStartReceiver extends BroadcastReceiver {
|
||||
private static final String TAG = AutoStartReceiver.class.getName();
|
||||
@@ -31,6 +32,7 @@ public class AutoStartReceiver extends BroadcastReceiver {
|
||||
if (GBApplication.getGBPrefs().getAutoStart() && Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
|
||||
Log.i(TAG, "Boot completed, starting Gadgetbridge");
|
||||
GBApplication.deviceService().start();
|
||||
PeriodicExporter.enablePeriodicExport(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ public class AppNotificationType extends HashMap<String, NotificationType> {
|
||||
put("com.fsck.k9.material", NotificationType.GENERIC_EMAIL);
|
||||
put("com.imaeses.squeaky", NotificationType.GENERIC_EMAIL);
|
||||
put("com.android.email", NotificationType.GENERIC_EMAIL);
|
||||
put("ch.protonmail.android", NotificationType.GENERIC_EMAIL);
|
||||
|
||||
// Generic SMS
|
||||
put("com.moez.QKSMS", NotificationType.GENERIC_SMS);
|
||||
@@ -68,6 +69,9 @@ public class AppNotificationType extends HashMap<String, NotificationType> {
|
||||
|
||||
// Telegram
|
||||
put("org.telegram.messenger", NotificationType.TELEGRAM);
|
||||
put("org.telegram.messenger.beta", NotificationType.TELEGRAM);
|
||||
put("org.telegram.plus", NotificationType.TELEGRAM); // "Plus Messenger"
|
||||
put("org.thunderdog.challegram", NotificationType.TELEGRAM);
|
||||
|
||||
// Threema
|
||||
put("ch.threema.app", NotificationType.THREEMA);
|
||||
@@ -125,6 +129,9 @@ public class AppNotificationType extends HashMap<String, NotificationType> {
|
||||
// Microsoft Outlook
|
||||
put("com.microsoft.office.outlook", NotificationType.OUTLOOK);
|
||||
|
||||
// Business Calendar
|
||||
put("com.appgenix.bizcal", NotificationType.BUSINESS_CALENDAR);
|
||||
|
||||
// Yahoo Mail
|
||||
put("com.yahoo.mobile.client.android.mail", NotificationType.YAHOO_MAIL);
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ public enum NotificationType {
|
||||
LINKEDIN(PebbleIconID.NOTIFICATION_LINKEDIN, PebbleColor.CobaltBlue),
|
||||
MAILBOX(PebbleIconID.NOTIFICATION_MAILBOX, PebbleColor.VividCerulean),
|
||||
OUTLOOK(PebbleIconID.NOTIFICATION_OUTLOOK, PebbleColor.BlueMoon),
|
||||
BUSINESS_CALENDAR(PebbleIconID.TIMELINE_CALENDAR, PebbleColor.BlueMoon),
|
||||
RIOT(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.LavenderIndigo),
|
||||
SIGNAL(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.BlueMoon),
|
||||
SKYPE(PebbleIconID.NOTIFICATION_SKYPE, PebbleColor.VividCerulean),
|
||||
|
||||
+21
@@ -44,6 +44,7 @@ import java.util.Objects;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.FindPhoneActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AbstractAppManagerFragment;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsHost;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
@@ -51,6 +52,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
|
||||
@@ -153,6 +155,25 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
|
||||
handleGBDeviceEvent((GBDeviceEventNotificationControl) deviceEvent);
|
||||
} else if (deviceEvent instanceof GBDeviceEventBatteryInfo) {
|
||||
handleGBDeviceEvent((GBDeviceEventBatteryInfo) deviceEvent);
|
||||
} else if (deviceEvent instanceof GBDeviceEventFindPhone) {
|
||||
handleGBDeviceEvent((GBDeviceEventFindPhone) deviceEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleGBDeviceEvent(GBDeviceEventFindPhone deviceEvent) {
|
||||
Context context = getContext();
|
||||
LOG.info("Got GBDeviceEventFindPhone");
|
||||
switch (deviceEvent.event) {
|
||||
case START:
|
||||
Intent startIntent = new Intent(getContext(), FindPhoneActivity.class);
|
||||
context.startActivity(startIntent);
|
||||
break;
|
||||
case STOP:
|
||||
Intent intent = new Intent(FindPhoneActivity.ACTION_FOUND);
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
|
||||
break;
|
||||
default:
|
||||
LOG.warn("unknown GBDeviceEventFindPhone");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-2
@@ -40,6 +40,11 @@ public class AmazfitBipFirmwareInfo extends HuamiFirmwareInfo {
|
||||
(byte) 0xfa, (byte) 0xf3, 0x62, (byte) 0xeb, (byte) 0x92, (byte) 0xc6, (byte) 0xa1, (byte) 0xbb
|
||||
};
|
||||
|
||||
private static final byte[] GPS_HEADER4 = new byte[]{
|
||||
0x0b, 0x61, 0x53, (byte) 0xed, (byte) 0x83, (byte) 0xac, 0x07, 0x21,
|
||||
(byte) 0x8c, 0x36, 0x2e, (byte) 0x8c, (byte) 0x9c, 0x08, 0x54, (byte) 0xa6
|
||||
};
|
||||
|
||||
// guessed - at least it is the same across versions from 0.0.7.x to 0.0.9.x
|
||||
// and different from other devices
|
||||
private static final byte[] FW_HEADER = new byte[]{
|
||||
@@ -79,9 +84,12 @@ public class AmazfitBipFirmwareInfo extends HuamiFirmwareInfo {
|
||||
crcToVersion.put(28586, "0.1.0.08");
|
||||
crcToVersion.put(26714, "0.1.0.11");
|
||||
crcToVersion.put(64160, "0.1.0.17");
|
||||
crcToVersion.put(21992, "0.1.0.26");
|
||||
crcToVersion.put(43028, "0.1.0.27");
|
||||
crcToVersion.put(59462, "0.1.0.33");
|
||||
crcToVersion.put(55277, "0.1.0.39");
|
||||
crcToVersion.put(47685, "0.1.0.43");
|
||||
crcToVersion.put(30229, "0.1.0.45");
|
||||
|
||||
// resources
|
||||
crcToVersion.put(12586, "0.0.8.74");
|
||||
@@ -93,12 +101,13 @@ public class AmazfitBipFirmwareInfo extends HuamiFirmwareInfo {
|
||||
crcToVersion.put(12098, "0.1.0.17");
|
||||
crcToVersion.put(28696, "0.1.0.26-0.1.0.27");
|
||||
crcToVersion.put(5650, "0.1.0.33");
|
||||
crcToVersion.put(16117, "0.1.0.39");
|
||||
crcToVersion.put(16117, "0.1.0.39-0.1.0.45");
|
||||
|
||||
// gps
|
||||
crcToVersion.put(61520, "9367,8f79a91,0,0,");
|
||||
crcToVersion.put(8784, "9565,dfbd8fa,0,0,");
|
||||
crcToVersion.put(16716, "9565,dfbd8faf42,0");
|
||||
crcToVersion.put(54154, "9567,8b05506,0,0,");
|
||||
}
|
||||
|
||||
public AmazfitBipFirmwareInfo(byte[] bytes) {
|
||||
@@ -113,7 +122,7 @@ public class AmazfitBipFirmwareInfo extends HuamiFirmwareInfo {
|
||||
}
|
||||
return HuamiFirmwareType.RES;
|
||||
}
|
||||
if (ArrayUtils.startsWith(bytes, GPS_HEADER) || ArrayUtils.startsWith(bytes, GPS_HEADER2) || ArrayUtils.startsWith(bytes, GPS_HEADER3)) {
|
||||
if (ArrayUtils.startsWith(bytes, GPS_HEADER) || ArrayUtils.startsWith(bytes, GPS_HEADER2) || ArrayUtils.startsWith(bytes, GPS_HEADER3) || ArrayUtils.startsWith(bytes, GPS_HEADER4)) {
|
||||
return HuamiFirmwareType.GPS;
|
||||
}
|
||||
if (ArrayUtils.startsWith(bytes, GPS_ALMANAC_HEADER)) {
|
||||
|
||||
+16
@@ -53,6 +53,8 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.F
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Version;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service.ENDPOINT_DISPLAY_ITEMS;
|
||||
|
||||
public class AmazfitBipSupport extends MiBand2Support {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AmazfitBipSupport.class);
|
||||
@@ -122,6 +124,19 @@ public class AmazfitBipSupport extends MiBand2Support {
|
||||
// ignore
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AmazfitBipSupport setDisplayItems(TransactionBuilder builder) {
|
||||
/*
|
||||
LOG.info("Enabling all display items");
|
||||
|
||||
// This will brick the watch, don't enable it!
|
||||
byte[] data = new byte[]{ENDPOINT_DISPLAY_ITEMS, (byte) 0xff, 0x01, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
|
||||
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), data);
|
||||
*/
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendWeather(WeatherSpec weatherSpec) {
|
||||
if (gbDevice.getFirmwareVersion() == null) {
|
||||
@@ -321,6 +336,7 @@ public class AmazfitBipSupport extends MiBand2Support {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void phase2Initialize(TransactionBuilder builder) {
|
||||
super.phase2Initialize(builder);
|
||||
|
||||
+1
@@ -78,6 +78,7 @@ public class HuamiIcon {
|
||||
case GENERIC_SMS:
|
||||
return WECHAT;
|
||||
case GENERIC_CALENDAR:
|
||||
case BUSINESS_CALENDAR:
|
||||
return CALENDAR;
|
||||
case FACEBOOK:
|
||||
return FACEBOOK;
|
||||
|
||||
+4
-1
@@ -32,8 +32,11 @@ public class AmazfitCorFirmwareInfo extends HuamiFirmwareInfo {
|
||||
(byte) 0xfe, (byte) 0xe7, (byte) 0xfe, (byte) 0xe7, (byte) 0xfe, (byte) 0xe7, (byte) 0xfe, (byte) 0xe7
|
||||
};
|
||||
|
||||
//FIXME: this is a moving target :/
|
||||
private static final int FW_HEADER_OFFSET = 0x9330;
|
||||
private static final int FW_HEADER_OFFSET_2 = 0x9340;
|
||||
private static final int FW_HEADER_OFFSET_3 = 0x9288;
|
||||
|
||||
private static final int NEW_RES_HEADER_OFFSET = 0x9;
|
||||
|
||||
private static Map<Integer, String> crcToVersion = new HashMap<>();
|
||||
@@ -62,7 +65,7 @@ public class AmazfitCorFirmwareInfo extends HuamiFirmwareInfo {
|
||||
return HuamiFirmwareType.RES;
|
||||
} else if (ArrayUtils.equals(bytes, RES_HEADER, NEW_RES_HEADER_OFFSET)) {
|
||||
return HuamiFirmwareType.RES_NEW;
|
||||
} else if (ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET) || ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET_2)) {
|
||||
} else if (ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET) || ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET_2) || ArrayUtils.equals(bytes, FW_HEADER, FW_HEADER_OFFSET_3)) {
|
||||
// TODO: this is certainly not a correct validation, but it works for now
|
||||
return HuamiFirmwareType.FIRMWARE;
|
||||
}
|
||||
|
||||
+4
-1
@@ -47,7 +47,7 @@ public class Mi2FirmwareInfo extends HuamiFirmwareInfo {
|
||||
|
||||
private static final int FW_HEADER_OFFSET = 0x150;
|
||||
|
||||
protected static Map<Integer, String> crcToVersion = new HashMap<>();
|
||||
private static Map<Integer, String> crcToVersion = new HashMap<>();
|
||||
|
||||
static {
|
||||
// firmware
|
||||
@@ -57,6 +57,9 @@ public class Mi2FirmwareInfo extends HuamiFirmwareInfo {
|
||||
crcToVersion.put(51770, "1.0.1.34");
|
||||
crcToVersion.put(3929, "1.0.1.39");
|
||||
crcToVersion.put(47364, "1.0.1.54");
|
||||
crcToVersion.put(44776, "1.0.1.59");
|
||||
crcToVersion.put(27318, "1.0.1.67");
|
||||
crcToVersion.put(54702, "1.0.1.69");
|
||||
|
||||
// fonts
|
||||
crcToVersion.put(45624, "Font");
|
||||
|
||||
+23
-3
@@ -54,10 +54,12 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiFWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.amazfitbip.AmazfitBipService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.huami.miband2.MiBand2FWHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.DateTimeDisplay;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.DoNotDisturb;
|
||||
@@ -160,6 +162,8 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
|
||||
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
|
||||
private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
|
||||
private final GBDeviceEventFindPhone findPhoneEvent = new GBDeviceEventFindPhone();
|
||||
|
||||
private RealtimeSamplesSupport realtimeSamplesSupport;
|
||||
private boolean alarmClockRinging;
|
||||
|
||||
@@ -966,16 +970,32 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
LOG.info("Tick 30 min (?)");
|
||||
break;
|
||||
case HuamiDeviceEvent.FIND_PHONE_START:
|
||||
LOG.info("find phone started (not yet supported)");
|
||||
LOG.info("find phone started");
|
||||
acknowledgeFindPhone(); // FIXME: premature
|
||||
findPhoneEvent.event = GBDeviceEventFindPhone.Event.START;
|
||||
evaluateGBDeviceEvent(findPhoneEvent);
|
||||
break;
|
||||
case HuamiDeviceEvent.FIND_PHONE_STOP:
|
||||
LOG.info("find phone stopped (not yet supported)");
|
||||
LOG.info("find phone stopped");
|
||||
findPhoneEvent.event = GBDeviceEventFindPhone.Event.STOP;
|
||||
evaluateGBDeviceEvent(findPhoneEvent);
|
||||
break;
|
||||
default:
|
||||
LOG.warn("unhandled event " + value[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private void acknowledgeFindPhone() {
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("acknowledge find phone");
|
||||
|
||||
builder.write(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), AmazfitBipService.COMMAND_ACK_FIND_PHONE_IN_PROGRESS);
|
||||
builder.queue(getQueue());
|
||||
} catch (Exception ex) {
|
||||
LOG.error("Error sending current weather", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void handleButtonEvent() {
|
||||
///logMessageContent(value);
|
||||
|
||||
@@ -1419,7 +1439,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
return this;
|
||||
}
|
||||
|
||||
private MiBand2Support setDisplayItems(TransactionBuilder builder) {
|
||||
protected MiBand2Support setDisplayItems(TransactionBuilder builder) {
|
||||
Set<String> pages = HuamiCoordinator.getDisplayItems();
|
||||
LOG.info("Setting display items to " + (pages == null ? "none" : pages));
|
||||
|
||||
|
||||
+2
-1
@@ -34,6 +34,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
|
||||
|
||||
class AppMessageHandler {
|
||||
final PebbleProtocol mPebbleProtocol;
|
||||
@@ -73,7 +74,7 @@ class AppMessageHandler {
|
||||
}
|
||||
|
||||
JSONObject getAppKeys() throws IOException, JSONException {
|
||||
File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache");
|
||||
File destDir = PebbleUtils.getPbwCacheDir();
|
||||
File configurationFile = new File(destDir, mUUID.toString() + ".json");
|
||||
if (configurationFile.exists()) {
|
||||
String jsonstring = FileUtils.getStringFromFile(configurationFile);
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble;
|
||||
|
||||
import android.util.Pair;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
|
||||
|
||||
public class AppMessageHandlerGBPebble extends AppMessageHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AppMessageHandlerMisfit.class);
|
||||
|
||||
private static final int KEY_FIND_PHONE_START = 1;
|
||||
private static final int KEY_FIND_PHONE_STOP = 2;
|
||||
|
||||
|
||||
AppMessageHandlerGBPebble(UUID uuid, PebbleProtocol pebbleProtocol) {
|
||||
super(uuid, pebbleProtocol);
|
||||
}
|
||||
|
||||
@Override
|
||||
public GBDeviceEvent[] handleMessage(ArrayList<Pair<Integer, Object>> pairs) {
|
||||
GBDeviceEventFindPhone gbDeviceEventFindPhone = null;
|
||||
|
||||
for (Pair<Integer, Object> pair : pairs) {
|
||||
switch (pair.first) {
|
||||
case KEY_FIND_PHONE_START:
|
||||
LOG.info("find phone start");
|
||||
gbDeviceEventFindPhone = new GBDeviceEventFindPhone();
|
||||
gbDeviceEventFindPhone.event = GBDeviceEventFindPhone.Event.START;
|
||||
break;
|
||||
case KEY_FIND_PHONE_STOP:
|
||||
LOG.info("find phone stop");
|
||||
gbDeviceEventFindPhone = new GBDeviceEventFindPhone();
|
||||
gbDeviceEventFindPhone.event = GBDeviceEventFindPhone.Event.STOP;
|
||||
break;
|
||||
default:
|
||||
LOG.info("unhandled key: " + pair.first);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// always ack
|
||||
GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes();
|
||||
sendBytesAck.encodedBytes = mPebbleProtocol.encodeApplicationMessageAck(mUUID, mPebbleProtocol.last_id);
|
||||
|
||||
return new GBDeviceEvent[]{sendBytesAck, gbDeviceEventFindPhone};
|
||||
}
|
||||
}
|
||||
+42
-7
@@ -25,6 +25,8 @@ import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelUuid;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -59,7 +61,6 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.ble.PebbleLESupport;
|
||||
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;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
@@ -100,12 +101,45 @@ class PebbleIoThread extends GBDeviceIoThread {
|
||||
private int mBytesWritten = -1;
|
||||
|
||||
private void sendAppMessageJS(GBDeviceEventAppMessage appMessage) {
|
||||
WebViewSingleton.appMessage(appMessage);
|
||||
sendAppMessage(appMessage);
|
||||
if (appMessage.type == GBDeviceEventAppMessage.TYPE_APPMESSAGE) {
|
||||
write(mPebbleProtocol.encodeApplicationMessageAck(appMessage.appUUID, (byte) appMessage.id));
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendAppMessage(GBDeviceEventAppMessage message) {
|
||||
final String jsEvent;
|
||||
try {
|
||||
WebViewSingleton.checkAppRunning(message.appUUID);
|
||||
} catch (IllegalStateException ex) {
|
||||
LOG.warn("Unable to send app message: " + message, ex);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: handle ACK and NACK types with ids
|
||||
if (message.type != GBDeviceEventAppMessage.TYPE_APPMESSAGE) {
|
||||
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 = PebbleUtils.parseIncomingAppMessage(message.message, message.appUUID);
|
||||
LOG.debug("to WEBVIEW: event: " + jsEvent + " message: " + appMessage);
|
||||
WebViewSingleton.invokeWebview(new WebViewSingleton.WebViewRunnable() {
|
||||
@Override
|
||||
public void invoke(WebView webView) {
|
||||
webView.evaluateJavascript("Pebble.evaluate('" + jsEvent + "',[" + appMessage + "]);", new ValueCallback<String>() {
|
||||
@Override
|
||||
public void onReceiveValue(String s) {
|
||||
//TODO: the message should be acked here instead of in PebbleIoThread
|
||||
LOG.debug("Callback from appmessage: " + s);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
PebbleIoThread(PebbleSupport pebbleSupport, GBDevice gbDevice, GBDeviceProtocol gbDeviceProtocol, BluetoothAdapter btAdapter, Context context) {
|
||||
super(gbDevice, context);
|
||||
mPebbleProtocol = (PebbleProtocol) gbDeviceProtocol;
|
||||
@@ -164,8 +198,9 @@ class PebbleIoThread extends GBDeviceIoThread {
|
||||
mOutStream = mBtSocket.getOutputStream();
|
||||
}
|
||||
}
|
||||
if (prefs.getBoolean("pebble_enable_background_javascript", false)) {
|
||||
if (GBApplication.getGBPrefs().isBackgroundJsEnabled()) {
|
||||
Intent startIntent = new Intent(getContext(), ExternalPebbleJSActivity.class);
|
||||
startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startIntent.putExtra(ExternalPebbleJSActivity.START_BG_WEBVIEW, true);
|
||||
getContext().startActivity(startIntent);
|
||||
} else {
|
||||
@@ -388,7 +423,7 @@ class PebbleIoThread extends GBDeviceIoThread {
|
||||
gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT);
|
||||
}
|
||||
|
||||
if (prefs.getBoolean("pebble_enable_background_javascript", false)) {
|
||||
if (GBApplication.getGBPrefs().isBackgroundJsEnabled()) {
|
||||
WebViewSingleton.disposeWebView();
|
||||
}
|
||||
|
||||
@@ -505,7 +540,7 @@ class PebbleIoThread extends GBDeviceIoThread {
|
||||
case REQUEST:
|
||||
LOG.info("APPFETCH request: " + appMgmt.uuid + " / " + appMgmt.token);
|
||||
try {
|
||||
installApp(Uri.fromFile(new File(FileUtils.getExternalFilesDir() + "/pbw-cache/" + appMgmt.uuid.toString() + ".pbw")), appMgmt.token);
|
||||
installApp(Uri.fromFile(new File(PebbleUtils.getPbwCacheDir(),appMgmt.uuid.toString() + ".pbw")), appMgmt.token);
|
||||
} catch (IOException e) {
|
||||
LOG.error("Error installing app: " + e.getMessage(), e);
|
||||
}
|
||||
@@ -516,7 +551,7 @@ class PebbleIoThread extends GBDeviceIoThread {
|
||||
break;
|
||||
case START:
|
||||
LOG.info("got GBDeviceEventAppManagement START event for uuid: " + appMgmt.uuid);
|
||||
if (prefs.getBoolean("pebble_enable_background_javascript", false)) {
|
||||
if (GBApplication.getGBPrefs().isBackgroundJsEnabled()) {
|
||||
if (mPebbleProtocol.hasAppMessageHandler(appMgmt.uuid)) {
|
||||
WebViewSingleton.stopJavascriptInterface();
|
||||
} else {
|
||||
@@ -534,7 +569,7 @@ class PebbleIoThread extends GBDeviceIoThread {
|
||||
setInstallSlot(appInfoEvent.freeSlot);
|
||||
return false;
|
||||
} else if (deviceEvent instanceof GBDeviceEventAppMessage) {
|
||||
if (GBApplication.getPrefs().getBoolean("pebble_enable_background_javascript", false)) {
|
||||
if (GBApplication.getGBPrefs().isBackgroundJsEnabled()) {
|
||||
sendAppMessageJS((GBDeviceEventAppMessage) deviceEvent);
|
||||
}
|
||||
if (mEnablePebblekit) {
|
||||
|
||||
+2
-1
@@ -407,7 +407,7 @@ 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));
|
||||
if (!(GBApplication.getPrefs().getBoolean("pebble_enable_background_javascript", false))) {
|
||||
if (!GBApplication.getGBPrefs().isBackgroundJsEnabled()) {
|
||||
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));
|
||||
@@ -418,6 +418,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
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_GBPEBBLE, new AppMessageHandlerGBPebble(UUID_GBPEBBLE, PebbleProtocol.this));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-6
@@ -16,6 +16,7 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import android.widget.Toast;
|
||||
|
||||
@@ -41,7 +42,6 @@ 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 {
|
||||
|
||||
@@ -51,7 +51,7 @@ public class JSInterface {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JSInterface.class);
|
||||
|
||||
public JSInterface(GBDevice device, UUID mUuid) {
|
||||
public JSInterface(@NonNull GBDevice device, @NonNull UUID mUuid) {
|
||||
LOG.debug("Creating JS interface for UUID: " + mUuid.toString());
|
||||
this.device = device;
|
||||
this.mUuid = mUuid;
|
||||
@@ -72,7 +72,11 @@ public class JSInterface {
|
||||
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);
|
||||
JSONObject knownKeys = PebbleUtils.getAppConfigurationKeys(this.mUuid);
|
||||
if (knownKeys == null) {
|
||||
LOG.warn("No app configuration keys for: " + mUuid);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
JSONObject in = new JSONObject(msg);
|
||||
@@ -139,7 +143,7 @@ public class JSInterface {
|
||||
public String getAppConfigurationFile() {
|
||||
LOG.debug("WEBVIEW loading config file of " + this.mUuid.toString());
|
||||
try {
|
||||
File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache");
|
||||
File destDir = PebbleUtils.getPbwCacheDir();
|
||||
File configurationFile = new File(destDir, this.mUuid.toString() + "_config.js");
|
||||
if (configurationFile.exists()) {
|
||||
return "file:///" + configurationFile.getAbsolutePath();
|
||||
@@ -153,7 +157,7 @@ public class JSInterface {
|
||||
@JavascriptInterface
|
||||
public String getAppStoredPreset() {
|
||||
try {
|
||||
File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache");
|
||||
File destDir = PebbleUtils.getPbwCacheDir();
|
||||
File configurationFile = new File(destDir, this.mUuid.toString() + "_preset.json");
|
||||
if (configurationFile.exists()) {
|
||||
return FileUtils.getStringFromFile(configurationFile);
|
||||
@@ -170,7 +174,7 @@ public class JSInterface {
|
||||
Writer writer;
|
||||
|
||||
try {
|
||||
File destDir = new File(FileUtils.getExternalFilesDir() + "/pbw-cache");
|
||||
File destDir = PebbleUtils.getPbwCacheDir();
|
||||
File presetsFile = new File(destDir, this.mUuid.toString() + "_preset.json");
|
||||
writer = new BufferedWriter(new FileWriter(presetsFile));
|
||||
writer.write(msg);
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelUuid;
|
||||
@@ -30,11 +31,16 @@ import android.support.v4.content.LocalBroadcastManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ActivitySummariesActivity;
|
||||
|
||||
public class AndroidUtils {
|
||||
/**
|
||||
@@ -126,6 +132,7 @@ public class AndroidUtils {
|
||||
+ Integer.toHexString(Color.blue(color));
|
||||
}
|
||||
|
||||
<<<<<<< HEAD
|
||||
public static void viewFile(String path, String action, Context context) throws IOException {
|
||||
Intent intent = new Intent(action);
|
||||
File file = new File(path);
|
||||
@@ -135,5 +142,63 @@ public class AndroidUtils {
|
||||
intent.setFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
intent.setData(contentUri);
|
||||
context.startActivity(intent);
|
||||
=======
|
||||
/**
|
||||
* As seen on stackoverflow https://stackoverflow.com/a/36714242/1207186
|
||||
* Try to find the file path of a document uri
|
||||
* @param context the application context
|
||||
* @param uri the Uri for which the path should be resolved
|
||||
* @return the path corresponding to the Uri as a String
|
||||
* @throws URISyntaxException
|
||||
*/
|
||||
public static String getFilePath(Context context, Uri uri) throws URISyntaxException {
|
||||
String selection = null;
|
||||
String[] selectionArgs = null;
|
||||
// Uri is different in versions after KITKAT (Android 4.4), we need to
|
||||
if (Build.VERSION.SDK_INT >= 19 && DocumentsContract.isDocumentUri(context.getApplicationContext(), uri)) {
|
||||
if ("com.android.externalstorage.documents".equals(uri.getAuthority())) {
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
return Environment.getExternalStorageDirectory() + "/" + split[1];
|
||||
} else if ("com.android.providers.downloads.documents".equals(uri.getAuthority())) {
|
||||
final String id = DocumentsContract.getDocumentId(uri);
|
||||
uri = ContentUris.withAppendedId(
|
||||
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
|
||||
} else if ("com.android.providers.media.documents".equals(uri.getAuthority())) {
|
||||
final String docId = DocumentsContract.getDocumentId(uri);
|
||||
final String[] split = docId.split(":");
|
||||
final String type = split[0];
|
||||
if ("image".equals(type)) {
|
||||
uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("video".equals(type)) {
|
||||
uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
|
||||
} else if ("audio".equals(type)) {
|
||||
uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
|
||||
}
|
||||
selection = "_id=?";
|
||||
selectionArgs = new String[]{
|
||||
split[1]
|
||||
};
|
||||
}
|
||||
}
|
||||
if ("content".equalsIgnoreCase(uri.getScheme())) {
|
||||
String[] projection = {
|
||||
MediaStore.Images.Media.DATA
|
||||
};
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = context.getContentResolver()
|
||||
.query(uri, projection, selection, selectionArgs, null);
|
||||
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
|
||||
if (cursor.moveToFirst()) {
|
||||
return cursor.getString(column_index);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
} else if ("file".equalsIgnoreCase(uri.getScheme())) {
|
||||
return uri.getPath();
|
||||
}
|
||||
return null;
|
||||
>>>>>>> master
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,13 @@ package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.icu.util.Output;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
@@ -32,6 +34,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
@@ -82,6 +85,22 @@ public class FileUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the contents of the given file to the destination output stream.
|
||||
* @param src the file from which to read.
|
||||
* @param dst the output stream that is written to. Note: the caller has to close the output stream!
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void copyFileToStream(File src, OutputStream dst) throws IOException {
|
||||
try (FileInputStream in = new FileInputStream(src)) {
|
||||
byte[] buf = new byte[4096];
|
||||
while(in.available() > 0) {
|
||||
int bytes = in.read(buf);
|
||||
dst.write(buf, 0, bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void copyURItoFile(Context ctx, Uri uri, File destFile) throws IOException {
|
||||
if (uri.getPath().equals(destFile.getPath())) {
|
||||
return;
|
||||
@@ -98,6 +117,24 @@ public class FileUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the content of a file to an uri,
|
||||
* which for example was retrieved using the storage access framework.
|
||||
* @param context the application context.
|
||||
* @param src the file from which the content should be copied.
|
||||
* @param dst the destination uri.
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void copyFileToURI(Context context, File src, Uri dst) throws IOException {
|
||||
OutputStream out = context.getContentResolver().openOutputStream(dst);
|
||||
if (out == null) {
|
||||
throw new IOException("Unable to open output stream for " + dst.toString());
|
||||
}
|
||||
try (OutputStream bufOut = new BufferedOutputStream(out)) {
|
||||
copyFileToStream(src, bufOut);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the textual contents of the given file. The contents is expected to be
|
||||
* in UTF-8 encoding.
|
||||
|
||||
@@ -44,6 +44,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBEnvironment;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
@@ -54,6 +55,7 @@ public class GB {
|
||||
public static final int NOTIFICATION_ID_INSTALL = 2;
|
||||
public static final int NOTIFICATION_ID_LOW_BATTERY = 3;
|
||||
public static final int NOTIFICATION_ID_TRANSFER = 4;
|
||||
public static final int NOTIFICATION_ID_EXPORT_FAILED = 5;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GB.class);
|
||||
public static final int INFO = 1;
|
||||
@@ -420,6 +422,37 @@ public class GB {
|
||||
removeNotification(NOTIFICATION_ID_LOW_BATTERY, context);
|
||||
}
|
||||
|
||||
public static Notification createExportFailedNotification(String text, Context context) {
|
||||
Intent notificationIntent = new Intent(context, SettingsActivity.class);
|
||||
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0,
|
||||
notificationIntent, 0);
|
||||
|
||||
NotificationCompat.Builder nb = new NotificationCompat.Builder(context)
|
||||
.setContentTitle(context.getString(R.string.notif_export_failed_title))
|
||||
.setContentText(text)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setPriority(Notification.PRIORITY_HIGH)
|
||||
.setOngoing(false);
|
||||
|
||||
return nb.build();
|
||||
}
|
||||
|
||||
public static void updateExportFailedNotification(String text, Context context) {
|
||||
if (GBEnvironment.env().isLocalTest()) {
|
||||
return;
|
||||
}
|
||||
Notification notification = createExportFailedNotification(text, context);
|
||||
updateNotification(notification, NOTIFICATION_ID_EXPORT_FAILED, context);
|
||||
}
|
||||
|
||||
public static void removeExportFailedNotification(Context context) {
|
||||
removeNotification(NOTIFICATION_ID_EXPORT_FAILED, context);
|
||||
}
|
||||
|
||||
|
||||
public static void assertThat(boolean condition, String errorMessage) {
|
||||
if (!condition) {
|
||||
throw new AssertionError(errorMessage);
|
||||
|
||||
@@ -24,7 +24,12 @@ public class GBPrefs {
|
||||
public static final String CALENDAR_BLACKLIST = "calendar_blacklist";
|
||||
public static final String AUTO_RECONNECT = "general_autocreconnect";
|
||||
private static final String AUTO_START = "general_autostartonboot";
|
||||
public static final String AUTO_EXPORT_ENABLED = "auto_export_enabled";
|
||||
public static final String AUTO_EXPORT_LOCATION = "auto_export_location";
|
||||
public static final String AUTO_EXPORT_INTERVAL = "auto_export_interval";
|
||||
private static final boolean AUTO_START_DEFAULT = true;
|
||||
private static final String BG_JS_ENABLED = "pebble_enable_background_javascript";
|
||||
private static final boolean BG_JS_ENABLED_DEFAULT = false;
|
||||
public static boolean AUTO_RECONNECT_DEFAULT = true;
|
||||
|
||||
public static final String USER_NAME = "mi_user_alias";
|
||||
@@ -45,6 +50,10 @@ public class GBPrefs {
|
||||
return mPrefs.getBoolean(AUTO_START, AUTO_START_DEFAULT);
|
||||
}
|
||||
|
||||
public boolean isBackgroundJsEnabled() {
|
||||
return mPrefs.getBoolean(BG_JS_ENABLED, BG_JS_ENABLED_DEFAULT);
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return mPrefs.getString(USER_NAME, USER_NAME_DEFAULT);
|
||||
}
|
||||
|
||||
@@ -61,8 +61,20 @@ public class LanguageUtils {
|
||||
|
||||
//ukrainian characters
|
||||
put('ґ', "gh"); put('є', "je"); put('і', "i"); put('ї', "ji"); put('Ґ', "GH"); put('Є', "JE"); put('І', "I"); put('Ї', "JI");
|
||||
|
||||
//TODO: these must be configurabe. If someone wants to transliterate cyrillic it does not mean his device has no German umlauts
|
||||
|
||||
// Arabic
|
||||
put('ا', "a"); put('ب', "b"); put('ت', "t"); put('ث', "th"); put('ج', "j"); put('ح', "7"); put('خ', "5");
|
||||
put('د', "d"); put('ذ', "th"); put('ر', "r"); put('ز', "z"); put('س', "s"); put('ش', "sh"); put('ص', "9");
|
||||
put('ض', "9'"); put('ط', "6"); put('ظ', "6'"); put('ع', "3"); put('غ', "3'"); put('ف', "f");
|
||||
put('ق', "q"); put('ك', "k"); put('ل', "l"); put('م', "m"); put('ن', "n"); put('ه', "h");
|
||||
put('و', "w"); put('ي', "y"); put('ى', "a");
|
||||
put('آ', "2"); put('ئ', "2"); put('إ', "2"); put('ؤ', "2"); put('أ', "2"); put('ء', "2");
|
||||
|
||||
// Farsi
|
||||
put('پ', "p"); put('چ', "ch"); put('ڜ', "ch"); put('ڤ', "v"); put('ڥ', "v");
|
||||
put('ڨ', "g"); put('گ', "g"); put('ݣ', "g");
|
||||
|
||||
//TODO: these must be configurable. If someone wants to transliterate cyrillic it does not mean his device has no German umlauts
|
||||
//all or nothing is really bad here
|
||||
}
|
||||
};
|
||||
|
||||
@@ -17,8 +17,22 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import org.json.JSONArray;
|
||||
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;
|
||||
|
||||
public class PebbleUtils {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PebbleUtils.class);
|
||||
|
||||
public static String getPlatformName(String hwRev) {
|
||||
String platformName;
|
||||
if (hwRev.startsWith("snowy")) {
|
||||
@@ -90,4 +104,74 @@ public class PebbleUtils {
|
||||
public static byte getPebbleColor(String colorHex) {
|
||||
return getPebbleColor(Color.parseColor(colorHex));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the directory containing the .pbw cache.
|
||||
* @throws IOException when the external files directory cannot be accessed
|
||||
*/
|
||||
public static File getPbwCacheDir() throws IOException {
|
||||
return new File(FileUtils.getExternalFilesDir(), "pbw-cache");
|
||||
}
|
||||
|
||||
public static JSONObject getAppConfigurationKeys(UUID uuid) {
|
||||
try {
|
||||
File destDir = getPbwCacheDir();
|
||||
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) {
|
||||
LOG.warn("Unable to parse configuration JSON file", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String parseIncomingAppMessage(String msg, UUID uuid) {
|
||||
JSONObject jsAppMessage = new JSONObject();
|
||||
|
||||
JSONObject knownKeys = PebbleUtils.getAppConfigurationKeys(uuid);
|
||||
SparseArray<String> appKeysMap = new SparseArray<>();
|
||||
|
||||
if (knownKeys == null || msg == null) {
|
||||
return "{}";
|
||||
}
|
||||
|
||||
String inKey, outKey;
|
||||
//knownKeys contains "name"->"index", we need to reverse that
|
||||
for (Iterator<String> 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<String> 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 (Exception e) {
|
||||
LOG.warn("Unable to parse incoming app message", e);
|
||||
}
|
||||
return jsAppMessage.toString();
|
||||
}
|
||||
}
|
||||
|
||||
+54
-130
@@ -31,29 +31,20 @@ import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Messenger;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.SparseArray;
|
||||
import android.webkit.ValueCallback;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
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;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.GBChromeClient;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.webview.GBWebClient;
|
||||
@@ -149,23 +140,34 @@ public class WebViewSingleton {
|
||||
return webViewSingleton.instance;
|
||||
}
|
||||
|
||||
public static void runJavascriptInterface(GBDevice device, UUID uuid) {
|
||||
if (uuid == null && device == null) {
|
||||
throw new RuntimeException("Javascript interface started without device and uuid");
|
||||
/**
|
||||
* Checks that the webview is up and the given app is running.
|
||||
* @param uuid the uuid of the application expected to be running
|
||||
* @throws IllegalStateException when the webview is not active or the app is not running
|
||||
*/
|
||||
public static void checkAppRunning(@NonNull UUID uuid) {
|
||||
if (webViewSingleton.instance == null) {
|
||||
throw new IllegalStateException("webViewSingleton.instance is null!");
|
||||
}
|
||||
if (!uuid.equals(currentRunningUUID)) {
|
||||
throw new IllegalStateException("Expected app " + uuid + " is not running, but " + currentRunningUUID + " is.");
|
||||
}
|
||||
}
|
||||
|
||||
public static void runJavascriptInterface(@NonNull GBDevice device, @NonNull UUID 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() {
|
||||
invokeWebview(new WebViewRunnable() {
|
||||
@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);
|
||||
public void invoke(WebView webView) {
|
||||
webView.onResume();
|
||||
webView.removeJavascriptInterface("GBjs");
|
||||
webView.addJavascriptInterface(jsInterface, "GBjs");
|
||||
webView.loadUrl("file:///android_asset/app_config/configure.html?rand=" + Math.random() * 500);
|
||||
}
|
||||
});
|
||||
if (!internetHelperBound) {
|
||||
@@ -177,51 +179,12 @@ 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: handle ACK and NACK types with ids
|
||||
if (message.type != GBDeviceEventAppMessage.TYPE_APPMESSAGE) {
|
||||
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: event: " + jsEvent + " message: " + appMessage);
|
||||
new Handler(webViewSingleton.mainLooper).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
webViewSingleton.instance.evaluateJavascript("Pebble.evaluate('" + jsEvent + "',[" + appMessage + "]);", new ValueCallback<String>() {
|
||||
@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 stopJavascriptInterface() {
|
||||
new Handler(webViewSingleton.mainLooper).post(new Runnable() {
|
||||
invokeWebview(new WebViewRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (webViewSingleton.instance != null) {
|
||||
webViewSingleton.instance.removeJavascriptInterface("GBjs");
|
||||
webViewSingleton.instance.loadUrl("about:blank");
|
||||
}
|
||||
public void invoke(WebView webView) {
|
||||
webView.removeJavascriptInterface("GBjs");
|
||||
webView.loadUrl("about:blank");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -233,86 +196,47 @@ public class WebViewSingleton {
|
||||
internetHelperBound = false;
|
||||
}
|
||||
currentRunningUUID = null;
|
||||
new Handler(webViewSingleton.mainLooper).post(new Runnable() {
|
||||
invokeWebview(new WebViewRunnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (webViewSingleton.instance != 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");
|
||||
// webViewSingleton.instance.freeMemory();
|
||||
webViewSingleton.instance.pauseTimers();
|
||||
public void invoke(WebView webView) {
|
||||
webView.removeJavascriptInterface("GBjs");
|
||||
// webView.setWebChromeClient(null);
|
||||
// webView.setWebViewClient(null);
|
||||
webView.clearHistory();
|
||||
webView.clearCache(true);
|
||||
webView.loadUrl("about:blank");
|
||||
// webView.freeMemory();
|
||||
webView.pauseTimers();
|
||||
// instance.destroy();
|
||||
// instance = null;
|
||||
// contextWrapper = null;
|
||||
// jsInterface = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public 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) {
|
||||
LOG.warn("Unable to parse configuration JSON file", e);
|
||||
public static void invokeWebview(final WebViewRunnable runnable) {
|
||||
if (webViewSingleton.instance == null || webViewSingleton.mainLooper == null) {
|
||||
LOG.warn("Webview already disposed, ignoring runnable");
|
||||
return;
|
||||
}
|
||||
return null;
|
||||
new Handler(webViewSingleton.mainLooper).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (webViewSingleton.instance == null) {
|
||||
LOG.warn("Webview already disposed, cannot invoke runnable");
|
||||
return;
|
||||
}
|
||||
runnable.invoke(webViewSingleton.instance);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static String parseIncomingAppMessage(String msg, UUID uuid) {
|
||||
JSONObject jsAppMessage = new JSONObject();
|
||||
|
||||
JSONObject knownKeys = getAppConfigurationKeys(uuid);
|
||||
SparseArray<String> appKeysMap = new SparseArray<>();
|
||||
|
||||
if (knownKeys == null || msg == null) {
|
||||
return "{}";
|
||||
}
|
||||
|
||||
String inKey, outKey;
|
||||
//knownKeys contains "name"->"index", we need to reverse that
|
||||
for (Iterator<String> 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<String> 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 (Exception e) {
|
||||
LOG.warn("Unable to parse incoming app message", e);
|
||||
}
|
||||
return jsAppMessage.toString();
|
||||
public interface WebViewRunnable {
|
||||
/**
|
||||
* Called in the main thread with a non-null webView instance
|
||||
* @param webView the webview, never null
|
||||
*/
|
||||
void invoke(WebView webView);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:isScrollContainer="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="10dp">
|
||||
|
||||
<Button
|
||||
android:id="@+id/foundbutton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:text="FOUND IT" />
|
||||
</LinearLayout>
|
||||
@@ -429,4 +429,5 @@
|
||||
\n
|
||||
\nEXPERIMENTÁLNÍ PROCES, DĚLÁTE NA VAŠE VLASTNÍ RIZIKO</string>
|
||||
<string name="amazfitbip_firmware">"Firmware Amazfit Bipu %1$s"</string>
|
||||
</resources>
|
||||
<string name="controlcenter_connect">Připojit</string>
|
||||
</resources>
|
||||
|
||||
@@ -156,7 +156,7 @@
|
||||
<string name="test_notification">Test Benachrichtigung</string>
|
||||
<string name="this_is_a_test_notification_from_gadgetbridge">Dies ist eine Testbenachrichtigung von Gadgetbridge</string>
|
||||
<string name="bluetooth_is_not_supported_">Bluetooth wird nicht unterstützt.</string>
|
||||
<string name="bluetooth_is_disabled_">Bluetooth ist abgeschaltet.</string>
|
||||
<string name="bluetooth_is_disabled_">Bluetooth ist deaktiviert.</string>
|
||||
<string name="tap_connected_device_for_app_mananger">Tippe auf das verbundene Gerät, um den App-Manager zu starten</string>
|
||||
<string name="tap_connected_device_for_activity">Tippe auf das verbundene Gerät, um die Aktivitätsdaten anzuzeigen</string>
|
||||
<string name="tap_connected_device_for_vibration">Tippe auf das verbundene Gerät, um es vibrieren zu lassen</string>
|
||||
@@ -455,4 +455,23 @@
|
||||
<string name="automatic">automatisch</string>
|
||||
<string name="activity_web_view">Web View Aktivität</string>
|
||||
|
||||
<string name="pref_title_weather">Wetter</string>
|
||||
<string name="kind_firmware">Firmware</string>
|
||||
<string name="kind_invalid">Ungültige Daten</string>
|
||||
<string name="kind_gps">GPS Firmware</string>
|
||||
<string name="devicetype_unknown">Unbekanntes Gerät</string>
|
||||
<string name="devicetype_test">Testgerät</string>
|
||||
<string name="devicetype_pebble">Pebble</string>
|
||||
<string name="devicetype_miband">Mi Band</string>
|
||||
<string name="devicetype_miband2">Mi Band 2</string>
|
||||
<string name="devicetype_amazfit_bip">Amazfit Bip</string>
|
||||
<string name="devicetype_amazfit_cor">Amazfit Cor</string>
|
||||
<string name="devicetype_vibratissimo">Vibratissimo</string>
|
||||
<string name="devicetype_liveview">LiveView</string>
|
||||
<string name="devicetype_hplus">HPlus</string>
|
||||
<string name="devicetype_makibes_f68">Makibes F68</string>
|
||||
<string name="devicetype_exrizu_k8">Exrizu K8</string>
|
||||
<string name="devicetype_no1_f1">No.1 F1</string>
|
||||
<string name="devicetype_teclast_h30">Teclast H30</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<string name="appmananger_app_delete">Borrar</string>
|
||||
<string name="appmananger_app_delete_cache">Borrar y quitar de la caché</string>
|
||||
<string name="appmananger_app_reinstall">Reinstalar</string>
|
||||
<string name="appmanager_app_openinstore">Buscar en la Appstore de Pebble</string>
|
||||
<string name="appmanager_app_openinstore">Buscar en la appstore de Pebble</string>
|
||||
<string name="appmanager_health_activate">Activar</string>
|
||||
<string name="appmanager_health_deactivate">Desactivar</string>
|
||||
<string name="appmanager_hrm_activate">Activar Monitor de Ritmo Cardíaco</string>
|
||||
@@ -403,7 +403,13 @@
|
||||
<string name="discovery_pair_question">Selecciona Emparejar para emparejar tus dispositivos. Si esto falla, prueba de nuevo sin emparejar.</string>
|
||||
<string name="discovery_yes_pair">Emparejar</string>
|
||||
<string name="discovery_dont_pair">No emparejar</string>
|
||||
<string name="fw_upgrade_notice_amazfitbip">Estás a punto de instalar el firmware %s en tu Amazfit Bip.
|
||||
<string name="fw_upgrade_notice_amazfitbip">Estás a punto de instalar el firmware %s en tu Amazfit Bip.
|
||||
\n
|
||||
\nPor favor, asegúrate de instalar el firmware .gps, luego el archivo .res, y finalmente el .fw. Tu reloj se reiniciará después de instalar el archivo .fw.
|
||||
\n
|
||||
\nNota: no tienes que instalar los .res y .gps si ya están instalados en su última versión.
|
||||
\n
|
||||
\n¡PROCEDE BAJO TU RESPONSABILIDAD!
|
||||
\n
|
||||
\nPor favor, asegúrate de instalar el firmware .gps, luego el archivo .res, y finalmente el binario .fw. Tu reloj reiniciará después de instalar el archivo .fw.
|
||||
\n
|
||||
|
||||
@@ -533,4 +533,21 @@
|
||||
<string name="devicetype_amazfit_bip">Amazfit Bip</string>
|
||||
<string name="devicetype_amazfit_cor">Amazfit Cor</string>
|
||||
<string name="devicetype_teclast_h30">Teclast H30</string>
|
||||
<string name="pref_title_weather">Meteoroloxía</string>
|
||||
<string name="pref_header_auto_export">Autoexportación</string>
|
||||
<string name="pref_title_auto_export_enabled">Autoexportación habilitada</string>
|
||||
<string name="pref_title_auto_export_location">Exportar localización</string>
|
||||
<string name="pref_title_auto_export_interval">Exportar intervalo</string>
|
||||
<string name="pref_summary_auto_export_interval">Exportar cada %d horas</string>
|
||||
|
||||
<string name="notif_export_failed_title">Fallou a exportación da base de datos! Por favor, comproba os teus axustes.</string>
|
||||
<string name="kind_watchface">Watchface</string>
|
||||
|
||||
<string name="devicetype_vibratissimo">Vibratissimo</string>
|
||||
<string name="devicetype_liveview">Vista en Vivo</string>
|
||||
<string name="devicetype_hplus">HPlus</string>
|
||||
<string name="devicetype_makibes_f68">Makibes F68</string>
|
||||
<string name="devicetype_exrizu_k8">Exrizu K8</string>
|
||||
<string name="devicetype_no1_f1">No.1 F1</string>
|
||||
<string name="choose_auto_export_location">Escolle lugar para exportar</string>
|
||||
</resources>
|
||||
|
||||
@@ -488,4 +488,12 @@
|
||||
<string name="devicetype_no1_f1">No.1 F1</string>
|
||||
<string name="devicetype_teclast_h30">Teclast H30</string>
|
||||
<string name="pref_title_weather">מזג אוויר</string>
|
||||
</resources>
|
||||
<string name="pref_header_auto_export">ייצוא אוטומטי</string>
|
||||
<string name="pref_title_auto_export_enabled">ייצוא אוטומטי מופעל</string>
|
||||
<string name="pref_title_auto_export_location">מיקום לייצוא</string>
|
||||
<string name="pref_title_auto_export_interval">הפרשים בין ייצוא לייצוא</string>
|
||||
<string name="pref_summary_auto_export_interval">לייצא בכל %d שעות</string>
|
||||
|
||||
<string name="notif_export_failed_title">ייצוא מסד הנתונים נכשל! נא לבדוק את ההגדרות שלך.</string>
|
||||
<string name="choose_auto_export_location">נא לבחור את מיקום הייצוא</string>
|
||||
</resources>
|
||||
|
||||
@@ -482,4 +482,12 @@
|
||||
<string name="devicetype_exrizu_k8">Exrizu K8</string>
|
||||
<string name="devicetype_no1_f1">No.1 F1</string>
|
||||
<string name="devicetype_teclast_h30">Teclast H30</string>
|
||||
<string name="pref_header_auto_export">自動エクスポート</string>
|
||||
<string name="pref_title_auto_export_enabled">自動エクスポートは有効です</string>
|
||||
<string name="pref_title_auto_export_location">エクスポートの場所</string>
|
||||
<string name="pref_title_auto_export_interval">エクスポート間隔</string>
|
||||
<string name="pref_summary_auto_export_interval">%d 時間ごとにエクスポート</string>
|
||||
|
||||
<string name="notif_export_failed_title">データベースのエクスポートが失敗しました! 設定を確認してください。</string>
|
||||
<string name="choose_auto_export_location">エクスポート先を選択</string>
|
||||
</resources>
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
<string name="action_quit">Quit</string>
|
||||
<string name="action_donate">Donate</string>
|
||||
<string name="controlcenter_fetch_activity_data">Synchronize</string>
|
||||
<string name="controlcenter_start_sleepmonitor">Sleep Monitor (ALPHA)</string>
|
||||
<string name="controlcenter_find_device">Find lost Device</string>
|
||||
<string name="controlcenter_take_screenshot">Take Screenshot</string>
|
||||
<string name="controlcenter_connect">Connect</string>
|
||||
@@ -34,7 +33,7 @@
|
||||
<string name="appmananger_app_delete">Delete</string>
|
||||
<string name="appmananger_app_delete_cache">Delete and remove from cache</string>
|
||||
<string name="appmananger_app_reinstall">Reinstall</string>
|
||||
<string name="appmanager_app_openinstore">Search in Pebble Appstore</string>
|
||||
<string name="appmanager_app_openinstore">Search in Pebble appstore</string>
|
||||
<string name="appmanager_health_activate">Activate</string>
|
||||
<string name="appmanager_health_deactivate">Deactivate</string>
|
||||
<string name="appmanager_hrm_activate">Activate HRM</string>
|
||||
@@ -54,12 +53,12 @@
|
||||
<!-- Strings related to FwAppInstaller -->
|
||||
<string name="title_activity_fw_app_insaller">FW/App installer</string>
|
||||
<string name="fw_upgrade_notice">You are about to install firmware %s instead of the one currently on your Mi Band.</string>
|
||||
<string name="fw_upgrade_notice_amazfitbip">You are about to install firmware %s on your Amazfit Bip.\n\nPlease make sure to install the .gps firmware, then the .res file, and finally the .fw file. Your watch will reboot after installing the .fw file.\n\nNote: You do not have to install .res and .gps if these files are exactly the same as the ones previously installed.\n\nPROCEED AT YOUR OWN RISK!</string>
|
||||
<string name="fw_upgrade_notice_amazfitcor">You are about to install firmware %s on your Amazfit Cor.\n\nPlease make sure to install the .res file, and after that the .fw file. Your watch will reboot after installing the .fw file.\n\nNote: You do not have to install .res if it is exactly the same as the one previously installed.\n\nUNTESTED, MAY BRICK YOUR DEVICE, PROCEED AT YOUR OWN RISK!</string>
|
||||
<string name="fw_multi_upgrade_notice">You are about to install firmwares %1$s and %2$s instead of the ones currently on your Mi Band.</string>
|
||||
<string name="fw_upgrade_notice_amazfitbip">You are about to install the %s firmware on your Amazfit Bip.\n\nPlease make sure to install the .gps firmware, then the .res file, and finally the .fw file. Your watch will reboot after installing the .fw file.\n\nNote: You do not have to install .res and .gps if these files are exactly the same as the ones previously installed.\n\nPROCEED AT YOUR OWN RISK!</string>
|
||||
<string name="fw_upgrade_notice_amazfitcor">You are about to install the %s firmware on your Amazfit Cor.\n\nPlease make sure to install the .res file, and after that the .fw file. Your watch will reboot after installing the .fw file.\n\nNote: You do not have to install .res if it is exactly the same as the one previously installed.\n\nUNTESTED, MAY BRICK YOUR DEVICE, PROCEED AT YOUR OWN RISK!</string>
|
||||
<string name="fw_multi_upgrade_notice">You are about to install the %1$s and %2$s firmware, instead of the ones currently on your Mi Band.</string>
|
||||
<string name="miband_firmware_known">This firmware has been tested and is known to be compatible with Gadgetbridge.</string>
|
||||
<string name="miband_firmware_unknown_warning">"This firmware is untested and may not be compatible with Gadgetbridge.\n\nYou are NOT encouraged to flash it to your Mi Band!"</string>
|
||||
<string name="miband_firmware_suggest_whitelist">If you still want to proceed and things continue to work properly afterwards, please tell the Gadgetbridge developers to whitelist firmware version: %s</string>
|
||||
<string name="miband_firmware_unknown_warning">"This firmware is untested and may not be compatible with Gadgetbridge.\n\nYou are DISCOURAGED from flashing it onto your Mi Band!"</string>
|
||||
<string name="miband_firmware_suggest_whitelist">If you still want to proceed and things continue to work properly afterwards, please tell the Gadgetbridge developers to whitelist the %s firmware version</string>
|
||||
|
||||
<!-- Strings related to Settings -->
|
||||
<string name="title_activity_settings">Settings</string>
|
||||
@@ -74,7 +73,7 @@
|
||||
|
||||
<string name="pref_header_datetime">Date and Time</string>
|
||||
<string name="pref_title_datetime_syctimeonconnect">Sync time</string>
|
||||
<string name="pref_summary_datetime_syctimeonconnect">Sync time to device when connecting and when time or timezone changes on Android</string>
|
||||
<string name="pref_summary_datetime_syctimeonconnect">Sync time to device when connecting, and when time or time zone changes on Android</string>
|
||||
|
||||
<string name="pref_title_theme">Theme</string>
|
||||
<string name="pref_theme_light">Light</string>
|
||||
@@ -95,7 +94,7 @@
|
||||
<string name="pref_title_notifications_generic">Generic notification support</string>
|
||||
<string name="pref_title_whenscreenon">…also when screen is on</string>
|
||||
<string name="pref_title_notification_filter">Do Not Disturb</string>
|
||||
<string name="pref_summary_notification_filter">Stop unwanted notifications from being sent based on the Do Not Disturb mode</string>
|
||||
<string name="pref_summary_notification_filter">Stop unwanted notifications from being sent in Do Not Disturb mode</string>
|
||||
<string name="pref_title_transliteration">Transliteration</string>
|
||||
<string name="pref_summary_transliteration">Enable this if your device has no support for your language\'s font</string>
|
||||
|
||||
@@ -168,7 +167,7 @@
|
||||
<string name="pref_title_pebble_forceuntested">Enable untested features</string>
|
||||
<string name="pref_summary_pebble_forceuntested">Enable features that are untested. ENABLE ONLY IF YOU KNOW WHAT YOU ARE DOING!</string>
|
||||
<string name="pref_title_pebble_forcele">Always prefer BLE</string>
|
||||
<string name="pref_summary_pebble_forcele">Use experimental Pebble LE support for all Pebbles instead of BT classic, requires paring a "Pebble LE" after non LE had been connected once</string>
|
||||
<string name="pref_summary_pebble_forcele">Use experimental Pebble LE support for all Pebbles, instead of BT classic. This requires paring to non LE first, and then "Pebble LE"</string>
|
||||
<string name="pref_title_pebble_mtu_limit">Pebble 2/LE GATT MTU limit</string>
|
||||
<string name="pref_summary_pebble_mtu_limit">If your Pebble 2/Pebble LE does not work as expected, try this setting to limit the MTU (valid range 20–512)</string>
|
||||
<string name="pref_title_pebble_enable_applogs">Enable watch App logging</string>
|
||||
@@ -187,6 +186,13 @@
|
||||
<string name="prefs_title_all_day_heart_rate">All day heart rate measurement</string>
|
||||
<string name="preferences_hplus_settings">HPlus/Makibes settings</string>
|
||||
|
||||
<!-- Auto export preferences -->
|
||||
<string name="pref_header_auto_export">Auto export</string>
|
||||
<string name="pref_title_auto_export_enabled">Auto export enabled</string>
|
||||
<string name="pref_title_auto_export_location">Export location</string>
|
||||
<string name="pref_title_auto_export_interval">Export interval</string>
|
||||
<string name="pref_summary_auto_export_interval">Export every %d hour</string>
|
||||
|
||||
<string name="not_connected">Not connected</string>
|
||||
<string name="connecting">Connecting</string>
|
||||
<string name="connected">Connected</string>
|
||||
@@ -205,7 +211,7 @@
|
||||
<string name="gadgetbridge_running">Gadgetbridge running</string>
|
||||
<string name="installing_binary_d_d">Installing binary %1$d/%2$d</string>
|
||||
<string name="installation_failed_">Installation failed</string>
|
||||
<string name="installation_successful">Installation successful</string>
|
||||
<string name="installation_successful">Installed</string>
|
||||
<string name="firmware_install_warning">YOU ARE TRYING TO INSTALL A FIRMWARE, PROCEED AT YOUR OWN RISK.\n\n\n This firmware is for HW Revision: %s</string>
|
||||
<string name="app_install_info">You are about to install the following app:\n\n\n%1$s Version %2$s by %3$s\n</string>
|
||||
<string name="n_a">N/A</string>
|
||||
@@ -299,24 +305,25 @@
|
||||
<string name="user_feedback_miband_set_alarms_ok">Alarms sent to device!</string>
|
||||
<string name="chart_no_data_synchronize">No data. Synchronize device?</string>
|
||||
<string name="user_feedback_miband_activity_data_transfer">About to transfer %1$s of data starting from %2$s</string>
|
||||
<string name="miband_prefs_fitness_goal">Target steps for each day</string>
|
||||
<string name="miband_prefs_fitness_goal">Daily step target</string>
|
||||
<string name="dbaccess_error_executing">Error executing \'%1$s\'</string>
|
||||
<string name="controlcenter_start_activitymonitor">Your activity (ALPHA)</string>
|
||||
<string name="cannot_connect">Cannot connect: %1$s</string>
|
||||
<string name="installer_activity_unable_to_find_handler">Unable to find a handler to install this file.</string>
|
||||
<string name="pbw_install_handler_unable_to_install">Unable to install the given file: %1$s</string>
|
||||
<string name="pbw_install_handler_hw_revision_mismatch">Unable to install the given firmware: it doesn\'t match your Pebble\'s hardware revision.</string>
|
||||
<string name="pbw_install_handler_hw_revision_mismatch">Unable to install the given firmware: It doesn\'t match your Pebble\'s hardware revision.</string>
|
||||
<string name="installer_activity_wait_while_determining_status">Please wait while determining the installation status…</string>
|
||||
<string name="notif_battery_low_title">Gadget battery Low!</string>
|
||||
<string name="notif_battery_low_percent">%1$s battery left: %2$s%%</string>
|
||||
<string name="notif_battery_low_bigtext_last_charge_time">Last charge: %s \n</string>
|
||||
<string name="notif_battery_low_bigtext_number_of_charges">Number of charges: %s</string>
|
||||
<string name="notif_export_failed_title">Export database failed! Please check your settings.</string>
|
||||
<string name="sleepchart_your_sleep">Your sleep</string>
|
||||
<string name="weeksleepchart_sleep_a_week">Sleep a week</string>
|
||||
<string name="weeksleepchart_sleep_a_week">Sleep per week</string>
|
||||
<string name="weeksleepchart_today_sleep_description">Sleep today, target: %1$s</string>
|
||||
<string name="weekstepschart_steps_a_week">Steps a week</string>
|
||||
<string name="weekstepschart_steps_a_week">Steps per week</string>
|
||||
<string name="activity_sleepchart_activity_and_sleep">Your activity and sleep</string>
|
||||
<string name="updating_firmware">Updating Firmware…</string>
|
||||
<string name="updating_firmware">Flashing firmware…</string>
|
||||
<string name="fwapp_install_device_not_ready">File cannot be installed, device not ready.</string>
|
||||
<string name="installhandler_firmware_name">%1$s: %2$s %3$s</string>
|
||||
<string name="miband_fwinstaller_compatible_version">Compatible version</string>
|
||||
@@ -330,7 +337,7 @@
|
||||
<string name="updatefirmwareoperation_metadata_updateproblem">Problem with the firmware metadata transfer</string>
|
||||
<string name="updatefirmwareoperation_update_complete">Firmware installation complete</string>
|
||||
<string name="updatefirmwareoperation_update_complete_rebooting">Firmware installation complete, rebooting device…</string>
|
||||
<string name="updatefirmwareoperation_write_failed">Firmware write failed</string>
|
||||
<string name="updatefirmwareoperation_write_failed">Firmware flashing failed</string>
|
||||
<string name="chart_steps">Steps</string>
|
||||
<string name="calories">Calories</string>
|
||||
<string name="distance">Distance</string>
|
||||
@@ -339,11 +346,11 @@
|
||||
<string name="battery">Battery</string>
|
||||
<string name="liveactivity_live_activity">Live activity</string>
|
||||
<string name="weeksteps_today_steps_description">Steps today, target: %1$s</string>
|
||||
<string name="pref_title_dont_ack_transfer">Do not ack activity data transfer</string>
|
||||
<string name="pref_title_dont_ack_transfer">Do not ACK activity data transfer</string>
|
||||
<string name="pref_summary_dont_ack_transfers">If the activity data are not acked to the band, they will not be cleared. Useful if GB is used together with other apps.</string>
|
||||
<string name="pref_summary_keep_data_on_device">Will keep activity data on the Mi Band even after synchronization. Useful if GB is used together with other apps.</string>
|
||||
<string name="pref_title_low_latency_fw_update">Use low-latency mode for Firmware updates</string>
|
||||
<string name="pref_summary_low_latency_fw_update">This might help on devices where firmware updates fail</string>
|
||||
<string name="pref_title_low_latency_fw_update">Use low-latency mode for firmware flashing</string>
|
||||
<string name="pref_summary_low_latency_fw_update">This might help on devices where firmware flashing fails</string>
|
||||
|
||||
<string name="live_activity_steps_history">Steps history</string>
|
||||
<string name="live_activity_current_steps_per_minute">Current steps/min</string>
|
||||
@@ -421,7 +428,7 @@
|
||||
<string name="device_fw">Firmware version: %1$s</string>
|
||||
<string name="error_creating_directory_for_logfiles">Error creating directory for log files: %1$s</string>
|
||||
<string name="DEVINFO_HR_VER">"HR: "</string>
|
||||
<string name="updatefirmwareoperation_update_in_progress">Firmware update in progress</string>
|
||||
<string name="updatefirmwareoperation_update_in_progress">Flashing firmware</string>
|
||||
<string name="updatefirmwareoperation_firmware_not_sent">Firmware not sent</string>
|
||||
<string name="charts_legend_heartrate">Heart rate</string>
|
||||
<string name="live_activity_heart_rate">Heart rate</string>
|
||||
@@ -438,16 +445,16 @@
|
||||
<string name="dbmanagementactivity_error_exporting_shared">"Error exporting preference: %1$s"</string>
|
||||
<string name="dbmanagementactivity_import_data_title">Import Data?</string>
|
||||
<string name="dbmanagementactivity_overwrite_database_confirmation">Really overwrite the current database? All your current activity data (if any) will be lost.</string>
|
||||
<string name="dbmanagementactivity_import_successful">Import successful.</string>
|
||||
<string name="dbmanagementactivity_import_successful">Imported.</string>
|
||||
<string name="dbmanagementactivity_error_importing_db">"Error importing DB: %1$s"</string>
|
||||
<string name="dbmanagementactivity_error_importing_shared">"Error importing preference: %1$s"</string>
|
||||
<string name="dbmanagementactivity_delete_activity_data_title">Delete Activity Data?</string>
|
||||
<string name="dbmanagementactivity_really_delete_entire_db">Really delete the entire database? All your activity data and information about your devices will be lost.</string>
|
||||
<string name="dbmanagementactivity_database_successfully_deleted">Data successfully deleted.</string>
|
||||
<string name="dbmanagementactivity_database_successfully_deleted">Data deleted.</string>
|
||||
<string name="dbmanagementactivity_db_deletion_failed">Database deletion failed.</string>
|
||||
<string name="dbmanagementactivity_delete_old_activity_db">Delete old Activity Database?</string>
|
||||
<string name="dbmanagementactivity_delete_old_activitydb_confirmation">Really delete the old activity database? Activity data that was not imported will be lost.</string>
|
||||
<string name="dbmanagementactivity_old_activity_db_successfully_deleted">Old activity data successfully deleted.</string>
|
||||
<string name="dbmanagementactivity_old_activity_db_successfully_deleted">Old activity data deleted.</string>
|
||||
<string name="dbmanagementactivity_old_activity_db_deletion_failed">Old Activity database deletion failed.</string>
|
||||
<string name="dbmanagementactivity_overwrite">Overwrite</string>
|
||||
<string name="Cancel">Cancel</string>
|
||||
@@ -458,7 +465,7 @@
|
||||
|
||||
<!-- Strings related to Pebble Pairing Activity-->
|
||||
<string name="title_activity_pebble_pairing">Pebble pairing</string>
|
||||
<string name="pebble_pairing_hint">A pairing dialog is expected to pop-up on your Android device. If that does not happen, look in the notification drawer and accept the pairing request. After that accept the pairing request on your Pebble</string>
|
||||
<string name="pebble_pairing_hint">A pairing dialog will pop up on your Android device. If not, look in the notification drawer and accept the pairing request. Also accept it on your Pebble afterwards</string>
|
||||
|
||||
<string name="weather_notification_label">Make sure that this skin is enabled in the Weather Notification app to get weather information on your Pebble.\n\nNo configuration is needed here.\n\nYou can enable the system weather app of your Pebble from the app management.\n\nSupported watchfaces will show the weather automatically.</string>
|
||||
<string name="pref_title_setup_bt_pairing">Enable Bluetooth pairing</string>
|
||||
@@ -488,7 +495,7 @@
|
||||
<string name="discovery_bonding_failed_immediately">Bonding with %1$s failed immediately.</string>
|
||||
<string name="discovery_trying_to_connect_to">Trying to connect to: %1$s</string>
|
||||
<string name="discovery_enable_bluetooth">Enable Bluetooth to discover devices.</string>
|
||||
<string name="discovery_successfully_bonded">Successfully bonded with %1$s.</string>
|
||||
<string name="discovery_successfully_bonded">Bound to %1$s.</string>
|
||||
<string name="discovery_pair_title">Pair with %1$s?</string>
|
||||
<string name="discovery_pair_question">Select Pair to pair your devices. If this fails, try again without pairing.</string>
|
||||
<string name="discovery_yes_pair">Pair</string>
|
||||
@@ -536,4 +543,6 @@
|
||||
<string name="devicetype_exrizu_k8">Exrizu K8</string>
|
||||
<string name="devicetype_no1_f1">No.1 F1</string>
|
||||
<string name="devicetype_teclast_h30">Teclast H30</string>
|
||||
|
||||
<string name="choose_auto_export_location">Choose export location</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<changelog>
|
||||
<release version="0.24.0" versioncode="117">
|
||||
<change>Fix logs sometimes not containing stacktraces</change>
|
||||
<change>Support periodic database export</change>
|
||||
<change>Support transliteration for Arabic and Farsi</change>
|
||||
<change>Try to make alarm details scrollable (for small devices)</change>
|
||||
<change>Amazfit Bip: Implement find phone feature</change>
|
||||
<change>Amazfit Bip: Support flashing latest GPS firmware</change>
|
||||
<change>Amazfit Cor: Support flashing latest firmware</change>
|
||||
<change>Pebble: Fix crash with experimental background javascript</change>
|
||||
<change>Charts: Several fixes to the MPAndroidChart library</change>
|
||||
</release>
|
||||
<release version="0.23.2" versioncode="116">
|
||||
<change>Mi Band 1S: Fix sync problem with firmware 4.16.11.15 (probably also Mi Band 1.0.15.0 and Mi Band 1A 5.16.11.15)</change>
|
||||
<change>Amazfit Cor: Fix problem with firmware >=1.0.6.27 being detected as Mi Band 2</change>
|
||||
|
||||
@@ -496,6 +496,24 @@
|
||||
</PreferenceScreen>
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:title="@string/pref_header_auto_export">
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="auto_export_enabled"
|
||||
android:title="@string/pref_title_auto_export_enabled" />
|
||||
<Preference
|
||||
android:key="auto_export_location"
|
||||
android:title="@string/pref_title_auto_export_location"
|
||||
android:summary="%s" />
|
||||
<EditTextPreference
|
||||
android:inputType="number"
|
||||
android:key="auto_export_interval"
|
||||
android:defaultValue="3"
|
||||
android:maxLength="3"
|
||||
android:title="@string/pref_title_auto_export_interval"
|
||||
android:summary="@string/pref_summary_auto_export_interval"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:key="pref_key_development"
|
||||
android:title="@string/pref_header_development">
|
||||
|
||||
@@ -27,7 +27,6 @@ public class LanguageUtilsTest extends TestBase {
|
||||
|
||||
@Test
|
||||
public void testStringTransliterateHebrew() throws Exception {
|
||||
//input with cyrillic and diacritic letters
|
||||
String input = "בדיקה עברית";
|
||||
String output = LanguageUtils.transliterate(input);
|
||||
String result = "bdykh 'bryth";
|
||||
@@ -35,6 +34,24 @@ public class LanguageUtilsTest extends TestBase {
|
||||
assertEquals("Transliteration failed", result, output);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStringTransliterateArabic() {
|
||||
String pangram = "نص حكيم له سر قاطع وذو شأن عظيم مكتوب على ثوب أخضر ومغلف بجلد أزرق";
|
||||
String pangramExpected = "n9 7kym lh sr qa63 wthw sh2n 36'ym mktwb 3la thwb 259'r wm3'lf bjld 2zrq";
|
||||
String pangramActual = LanguageUtils.transliterate(pangram);
|
||||
assertEquals("Arabic pangram transliteration failed", pangramExpected, pangramActual);
|
||||
|
||||
String hamza = "ءأؤإئآ";
|
||||
String hamzaExpected = "222222";
|
||||
String hamzaActual = LanguageUtils.transliterate(hamza);
|
||||
assertEquals("hamza transliteration failed", hamzaExpected, hamzaActual);
|
||||
|
||||
String farsi = "پچڜڤڥڨگݣ";
|
||||
String farsiExpected = "pchchvvggg";
|
||||
String farsiActual = LanguageUtils.transliterate(farsi);
|
||||
assertEquals("Farsi transiteration failed", farsiExpected, farsiActual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransliterateOption() throws Exception {
|
||||
setDefaultTransliteration();
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
* Fix logs sometimes not containing stacktraces
|
||||
* Support periodic database export
|
||||
* Support transliteration for Arabic and Farsi
|
||||
* Try to make alarm details scrollable (for small devices)
|
||||
* Amazfit Bip: Implement find phone feature
|
||||
* Amazfit Bip: Support flashing latest GPS firmware
|
||||
* Amazfit Cor: Support flashing latest firmware
|
||||
* Pebble: Fix crash with experimental background javascript
|
||||
* Charts: Several fixes to the MPAndroidChart library
|
||||
Reference in New Issue
Block a user