mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2025-01-25 08:05:55 +01:00
Merge branch 'master' into health-new-database
This commit is contained in:
commit
3bb255eb47
40
CHANGELOG.md
40
CHANGELOG.md
@ -1,23 +1,53 @@
|
||||
### Changelog
|
||||
|
||||
###Version 0.18.4
|
||||
#### Version 0.19.2
|
||||
* Pebble: Fix recurring calendar events only appearing once per week
|
||||
* HPlus: Fix crash when receiving calls without phone number
|
||||
* HPlus: Detect unicode support on Zeband Plus
|
||||
* No longer quit Gadgetbridge when bluetooth gets turned off
|
||||
|
||||
#### Version 0.19.1
|
||||
* Fix crash at startup
|
||||
* HPlus: Improve reconnection to device
|
||||
* Improve transliteration
|
||||
|
||||
#### Version 0.19.0
|
||||
* Pebble: allow calendar sync with Timeline (Title, Location, Description)
|
||||
* Pebble: display calendar icon for reminders from AOSP Calendar
|
||||
* HPlus: try to fix latin characters showing as random Chinese text
|
||||
* Improve reconnection with BLE devices
|
||||
* Improve generic notification reliability by trying to restart the notification listener when stale/crashed
|
||||
* Other small bugfixes
|
||||
|
||||
#### Version 0.18.5
|
||||
* Applied some material design guidelines to Charts and (pebble) app management
|
||||
* Changed colours: deep sleep is now dark blue, light sleep is now light blue
|
||||
* Support for exporting and importing of preferences in addition to the database
|
||||
* Visual improvements of the pie charts
|
||||
* Add filter by name in the App blacklist activity
|
||||
* Pebble: improve compatibility with watch app configuration pages
|
||||
* Pebble: display battery percentage (will only update once an hour)
|
||||
* HPlus: users can now decide whether they want to pair the device or not, hopefully fixing some connection problems (#642)
|
||||
* HPlus: display battery state and warn on low battery
|
||||
|
||||
#### Version 0.18.4
|
||||
* Mi Band 2: Display realtime steps in Live Activity
|
||||
* Mi Band: Attempt to recognize Mi Band model with hwVersion = 8
|
||||
* Alarms activity improvements and fixes
|
||||
* Make Buttons in the main activity easier to hit
|
||||
|
||||
###Version 0.18.3
|
||||
#### Version 0.18.3
|
||||
* Fix bug that caused the same value in weekly charts for every day on Android 6 and older
|
||||
|
||||
###Version 0.18.2
|
||||
#### Version 0.18.2
|
||||
* Mi Band 2: Fix crash on "chat" or "social network" text notification (#603)
|
||||
|
||||
###Version 0.18.1
|
||||
#### Version 0.18.1
|
||||
* Pebble: Fix Firmware insstallation on Pebble Time Round (broken since 0.16.0)
|
||||
* Start VibrationActivity when using "find device" button with Vibratissimo
|
||||
* Support material fork of K9
|
||||
|
||||
###Version 0.18.0
|
||||
#### Version 0.18.0
|
||||
* All new GUI for the control center
|
||||
* Add Portuguese pt_PT and pt_BR translations
|
||||
* Add Czech translation
|
||||
|
@ -2,7 +2,7 @@
|
||||
names ()
|
||||
{
|
||||
echo -e "\n exit;\n**Contributors (sorted by number of commits):**\n";
|
||||
git log --format='%aN:%aE' origin/master | sed 's/@users.github.com/@users.noreply.github.com/g' | awk 'BEGIN{FS=":"}{ct[$2]+=1;if (length($1) > length(e[$2])) {e[$2]=$1}}END{for (i in e) { n[e[i]]=i;c[e[i]]+=ct[i] }; for (a in n) print c[a]"\t* "a" <"n[a]">";}' | sort -n -r | cut -f 2-
|
||||
git log --format='%aN:%ae' origin/master | grep -Ev "FYG_.*_bot_ignore_me" | sed 's/@users.github.com/@users.noreply.github.com/g' | awk 'BEGIN{FS=":"}{ct[$1]+=1;if (length($2) > length(e[$1])) {e[$1]=$2}}END{for (i in e) { n[i]=e[i];c[i]+=ct[i] }; for (a in e) print c[a]"\t* "a" <"n[a]">";}' | sort -n -r | cut -f 2-
|
||||
}
|
||||
quine ()
|
||||
{
|
||||
@ -33,14 +33,17 @@
|
||||
* Sergey Trofimov <sarg@sarg.org.ru>
|
||||
* JohnnySun <bmy001@gmail.com>
|
||||
* Uwe Hermann <uwe@hermann-uwe.de>
|
||||
* Alberto <albertsal83@gmail.com>
|
||||
* 0nse <0nse@users.noreply.github.com>
|
||||
* Gergely Peidl <gergely@peidl.net>
|
||||
* Christian Fischer <sw-dev@computerlyrik.de>
|
||||
* 6arms1leg <m.brnsfld@googlemail.com>
|
||||
* walkjivefly <mark@walkjivefly.com>
|
||||
* Normano64 <per.bergqwist@gmail.com>
|
||||
* Avamander <Avamander@users.noreply.github.com>
|
||||
* Ⲇⲁⲛⲓ Φi <daniphii@outlook.com>
|
||||
* Yar <yaroslav.isakov@gmail.com>
|
||||
* Yaron Shahrabani <sh.yaron@gmail.com>
|
||||
* xzovy <caleb@caleb-cooper.net>
|
||||
* xphnx <xphnx@users.noreply.github.com>
|
||||
* Tarik Sekmen <tarik@ilixi.org>
|
||||
@ -57,6 +60,7 @@
|
||||
* Hasan Ammar <ammarh@gmail.com>
|
||||
* Gilles MOREL <contact@gilles-morel.fr>
|
||||
* Gilles Émilien MOREL <Almtesh@users.noreply.github.com>
|
||||
* Daniel Hauck <maill@dhauck.eu>
|
||||
* Chris Perelstein <chris.perelstein@gmail.com>
|
||||
* Carlos Ferreira <calbertoferreira@gmail.com>
|
||||
* atkyritsis <at.kyritsis@gmail.com>
|
||||
|
@ -17,6 +17,7 @@ package nodomain.freeyourgadget.gadgetbridge.daogen;
|
||||
|
||||
import de.greenrobot.daogenerator.DaoGenerator;
|
||||
import de.greenrobot.daogenerator.Entity;
|
||||
import de.greenrobot.daogenerator.Index;
|
||||
import de.greenrobot.daogenerator.Property;
|
||||
import de.greenrobot.daogenerator.Schema;
|
||||
|
||||
@ -40,6 +41,7 @@ public class GBDaoGenerator {
|
||||
private static final String TIMESTAMP_FROM = "timestampFrom";
|
||||
private static final String TIMESTAMP_TO = "timestampTo";
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Schema schema = new Schema(16, MAIN_PACKAGE + ".entities");
|
||||
|
||||
@ -65,6 +67,8 @@ public class GBDaoGenerator {
|
||||
addHPlusHealthActivityKindOverlay(schema, user, device);
|
||||
addHPlusHealthActivitySample(schema, user, device);
|
||||
|
||||
addCalendarSyncState(schema, device);
|
||||
|
||||
new DaoGenerator().generateAll(schema, "app/src/main/java");
|
||||
}
|
||||
|
||||
@ -292,6 +296,20 @@ public class GBDaoGenerator {
|
||||
activitySample.addToOne(user, userId);
|
||||
}
|
||||
|
||||
private static void addCalendarSyncState(Schema schema, Entity device) {
|
||||
Entity calendarSyncState = addEntity(schema, "CalendarSyncState");
|
||||
calendarSyncState.addIdProperty();
|
||||
Property deviceId = calendarSyncState.addLongProperty("deviceId").notNull().getProperty();
|
||||
Property calendarEntryId = calendarSyncState.addLongProperty("calendarEntryId").notNull().getProperty();
|
||||
Index indexUnique = new Index();
|
||||
indexUnique.addProperty(deviceId);
|
||||
indexUnique.addProperty(calendarEntryId);
|
||||
indexUnique.makeUnique();
|
||||
calendarSyncState.addIndex(indexUnique);
|
||||
calendarSyncState.addToOne(device, deviceId);
|
||||
calendarSyncState.addIntProperty("hash").notNull();
|
||||
}
|
||||
|
||||
private static Property findProperty(Entity entity, String propertyName) {
|
||||
for (Property prop : entity.getProperties()) {
|
||||
if (propertyName.equals(prop.getPropertyName())) {
|
||||
|
14
README.md
14
README.md
@ -15,7 +15,7 @@ need to create an account and transmit any of your data to the vendor's servers.
|
||||
|
||||
## Supported Devices
|
||||
* Pebble, Pebble Steel, Pebble Time, Pebble Time Steel, Pebble Time Round [Wiki section about this device](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble)
|
||||
* Pebble 2, Pebble Time 2 (experimental, PAIR WITHIN GADGETBRIDGE) [Wiki section about pebble](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble), most parts apply to Pebble 2 as well
|
||||
* Pebble 2 (add the device from within Gadgetbridge!) [Wiki section about pebble](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Pebble), most parts apply to Pebble 2 as well
|
||||
* Mi Band, Mi Band 1A, Mi Band 1S [Wiki section about this device](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Mi-Band)
|
||||
* Mi Band 2 [Wiki section about mi band](https://github.com/Freeyourgadget/Gadgetbridge/wiki/Mi-Band), some parts apply to mi band 2 as well
|
||||
* Vibratissimo (experimental)
|
||||
@ -26,13 +26,12 @@ need to create an account and transmit any of your data to the vendor's servers.
|
||||
|
||||
* Incoming calls notification and display
|
||||
* Outgoing call display
|
||||
* Reject/hangup calls
|
||||
* Reject calls (optionally with predefined texts) / hangup calls
|
||||
* SMS notification
|
||||
* K-9 Mail notification support
|
||||
* Support for generic notifications (above filtered out)
|
||||
* Support for up to 16 predefined replies for SMS and Android Wear compatible notifications (experimental, tested with Signal)
|
||||
* Support for generic notifications
|
||||
* Support for up to 16 predefined replies for SMS and Android Wear compatible notifications (experimental, tested with Signal and Conversations)
|
||||
* Dismiss individual notifications, mute or open corresponding app on phone from the action menu (generic notifications)
|
||||
* Dismiss all notifications from the action menu (non-generic notifications)
|
||||
* Dismiss all notifications from the action menu (SMS and PebbleKit notifications)
|
||||
* Music playback info (artist, album, track)
|
||||
* Music control: play/pause, next track, previous track, volume up, volume down
|
||||
* List and remove installed apps/watchfaces
|
||||
@ -41,7 +40,8 @@ need to create an account and transmit any of your data to the vendor's servers.
|
||||
* Install language files (.pbl)
|
||||
* Take and share screenshots from the Pebble's screen
|
||||
* PebbleKit support for 3rd Party Android Apps (experimental)
|
||||
* Fetch activity data from Pebble Health, Misfit and Morpheuz (experimental)
|
||||
* Fetch activity data from Pebble Health
|
||||
* Build-in support for Misfit and Morpheuz (experimental)
|
||||
* Configure watchfaces / apps (limited compatibility, experimental)
|
||||
|
||||
## Notes about Firmware >=3.0 (Pebble Time, updated OG)
|
||||
|
@ -26,8 +26,8 @@ android {
|
||||
targetSdkVersion 25
|
||||
|
||||
// note: always bump BOTH versionCode and versionName!
|
||||
versionName "0.18.4"
|
||||
versionCode 91
|
||||
versionName "0.19.2"
|
||||
versionCode 95
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
buildTypes {
|
||||
@ -53,7 +53,7 @@ android {
|
||||
}
|
||||
|
||||
pmd {
|
||||
toolVersion = '5.5.1'
|
||||
toolVersion = '5.5.5'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@ -61,7 +61,7 @@ dependencies {
|
||||
// testCompile 'ch.qos.logback:logback-core:1.1.3'
|
||||
testCompile 'junit:junit:4.12'
|
||||
testCompile "org.mockito:mockito-core:1.9.5"
|
||||
testCompile "org.robolectric:robolectric:3.2.2"
|
||||
testCompile "org.robolectric:robolectric:3.3.2"
|
||||
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.android.support:appcompat-v7:25.3.1'
|
||||
@ -69,14 +69,16 @@ dependencies {
|
||||
compile 'com.android.support:recyclerview-v7:25.3.1'
|
||||
compile 'com.android.support:support-v4:25.3.1'
|
||||
compile 'com.android.support:design:25.3.1'
|
||||
compile 'com.github.tony19:logback-android-classic:1.1.1-4'
|
||||
compile 'com.github.tony19:logback-android-classic:1.1.1-6'
|
||||
compile 'org.slf4j:slf4j-api:1.7.7'
|
||||
compile 'com.github.PhilJay:MPAndroidChart:v3.0.1'
|
||||
compile 'com.github.PhilJay:MPAndroidChart:v3.0.2'
|
||||
compile 'com.github.pfichtner:durationformatter:0.1.1'
|
||||
compile 'de.cketti.library.changelog:ckchangelog:1.2.2'
|
||||
compile 'net.e175.klaus:solarpositioning:0.0.9'
|
||||
compile 'com.github.freeyourgadget:greendao:1998d7cd2d21f662c6044f6ccf3b3a251bbad341'
|
||||
compile 'org.apache.commons:commons-lang3:3.4'
|
||||
// use pristine greendao instead of our custom version, since our custom jitpack-packaged
|
||||
// version contains way too much and our custom patches are in the generator only.
|
||||
compile 'org.greenrobot:greendao:2.2.1'
|
||||
compile 'org.apache.commons:commons-lang3:3.5'
|
||||
|
||||
// compile project(":DaoCore")
|
||||
}
|
||||
|
@ -251,6 +251,7 @@
|
||||
<action android:name="android.service.notification.NotificationListenerService" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service android:name=".service.NotificationCollectorMonitorService" />
|
||||
<service android:name=".service.DeviceCommunicationService" />
|
||||
|
||||
<receiver
|
||||
|
@ -1,7 +1,14 @@
|
||||
navigator.geolocation.getCurrentPosition = function(success, failure) { //override because default implementation requires GPS permission
|
||||
success(JSON.parse(GBjs.getCurrentPosition()));
|
||||
failure({ code: 2, message: "POSITION_UNAVAILABLE"});
|
||||
var reportedPositionFailures = 0;
|
||||
navigator.geolocation.getCurrentPosition = function(success, failure, options) { //override because default implementation requires GPS permission
|
||||
geoposition = JSON.parse(GBjs.getCurrentPosition());
|
||||
|
||||
if(options && options.maximumAge && (geoposition.timestamp < Date.now() - options.maximumAge) && reportedPositionFailures <= 10 ) {
|
||||
reportedPositionFailures++;
|
||||
failure({ code: 2, message: "POSITION_UNAVAILABLE"});
|
||||
} else {
|
||||
reportedPositionFailures = 0;
|
||||
success(geoposition);
|
||||
}
|
||||
}
|
||||
|
||||
if (window.Storage){
|
||||
@ -201,7 +208,7 @@ var storedPreset = GBjs.getAppStoredPreset();
|
||||
document.addEventListener('DOMContentLoaded', function(){
|
||||
if (jsConfigFile != null) {
|
||||
loadScript(jsConfigFile, function() {
|
||||
Pebble.evaluate('ready');
|
||||
Pebble.evaluate('ready', [{'type': "ready"}]); //callback object apparently needed by some watchfaces
|
||||
if (getURLVariable('config') == 'true') {
|
||||
showStep("step2");
|
||||
var json_string = getURLVariable('json');
|
||||
|
@ -40,6 +40,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
@ -53,6 +54,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.NotificationCollectorMonitorService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||
@ -86,12 +88,19 @@ public class GBApplication extends Application {
|
||||
|
||||
public static final String ACTION_QUIT
|
||||
= "nodomain.freeyourgadget.gadgetbridge.gbapplication.action.quit";
|
||||
|
||||
private static GBApplication app;
|
||||
|
||||
private static Logging logging = new Logging() {
|
||||
@Override
|
||||
protected String createLogDirectory() throws IOException {
|
||||
if (GBEnvironment.env().isLocalTest()) {
|
||||
return System.getProperty(Logging.PROP_LOGFILES_DIR);
|
||||
} else {
|
||||
File dir = FileUtils.getExternalFilesDir();
|
||||
return dir.getAbsolutePath();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private DeviceManager deviceManager;
|
||||
@ -108,12 +117,17 @@ public class GBApplication extends Application {
|
||||
// don't do anything here, add it to onCreate instead
|
||||
}
|
||||
|
||||
public static Logging getLogging() {
|
||||
return logging;
|
||||
}
|
||||
|
||||
protected DeviceService createDeviceService() {
|
||||
return new GBDeviceService(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
app = this;
|
||||
super.onCreate();
|
||||
|
||||
if (lockHandler != null) {
|
||||
@ -125,6 +139,13 @@ public class GBApplication extends Application {
|
||||
prefs = new Prefs(sharedPrefs);
|
||||
gbPrefs = new GBPrefs(prefs);
|
||||
|
||||
if (!GBEnvironment.isEnvironmentSetup()) {
|
||||
GBEnvironment.setupEnvironment(GBEnvironment.createDeviceEnvironment());
|
||||
// setup db after the environment is set up, but don't do it in test mode
|
||||
// in test mode, it's done individually, see TestBase
|
||||
setupDatabase();
|
||||
}
|
||||
|
||||
// don't do anything here before we set up logging, otherwise
|
||||
// slf4j may be implicitly initialized before we properly configured it.
|
||||
setupLogging(isFileLoggingEnabled());
|
||||
@ -135,10 +156,6 @@ public class GBApplication extends Application {
|
||||
|
||||
setupExceptionHandler();
|
||||
|
||||
GB.environment = GBEnvironment.createDeviceEnvironment();
|
||||
|
||||
setupDatabase(this);
|
||||
|
||||
deviceManager = new DeviceManager(this);
|
||||
|
||||
deviceService = createDeviceService();
|
||||
@ -146,6 +163,8 @@ public class GBApplication extends Application {
|
||||
|
||||
if (isRunningMarshmallowOrLater()) {
|
||||
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
//the following will ensure the notification manager is kept alive
|
||||
startService(new Intent(this, NotificationCollectorMonitorService.class));
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,8 +210,14 @@ public class GBApplication extends Application {
|
||||
return prefs.getBoolean("minimize_priority", false);
|
||||
}
|
||||
|
||||
static void setupDatabase(Context context) {
|
||||
DBOpenHelper helper = new DBOpenHelper(context, DATABASE_NAME, null);
|
||||
public void setupDatabase() {
|
||||
DaoMaster.OpenHelper helper;
|
||||
GBEnvironment env = GBEnvironment.env();
|
||||
if (env.isTest()) {
|
||||
helper = new DaoMaster.DevOpenHelper(this, null, null);
|
||||
} else {
|
||||
helper = new DBOpenHelper(this, DATABASE_NAME, null);
|
||||
}
|
||||
SQLiteDatabase db = helper.getWritableDatabase();
|
||||
DaoMaster daoMaster = new DaoMaster(db);
|
||||
if (lockHandler == null) {
|
||||
@ -310,10 +335,23 @@ public class GBApplication extends Application {
|
||||
return NotificationManager.INTERRUPTION_FILTER_ALL;
|
||||
}
|
||||
|
||||
public static HashSet<String> blacklist = null;
|
||||
private static HashSet<String> blacklist = null;
|
||||
|
||||
public static boolean isBlacklisted(String packageName) {
|
||||
return blacklist != null && blacklist.contains(packageName);
|
||||
}
|
||||
|
||||
public static void setBlackList(Set<String> packageNames) {
|
||||
if (packageNames == null) {
|
||||
blacklist = new HashSet<>();
|
||||
} else {
|
||||
blacklist = new HashSet<>(packageNames);
|
||||
}
|
||||
saveBlackList();
|
||||
}
|
||||
|
||||
private static void loadBlackList() {
|
||||
blacklist = (HashSet<String>) sharedPrefs.getStringSet("package_blacklist", null);
|
||||
blacklist = (HashSet<String>) sharedPrefs.getStringSet(GBPrefs.PACKAGE_BLACKLIST, null);
|
||||
if (blacklist == null) {
|
||||
blacklist = new HashSet<>();
|
||||
}
|
||||
@ -322,16 +360,15 @@ public class GBApplication extends Application {
|
||||
private static void saveBlackList() {
|
||||
SharedPreferences.Editor editor = sharedPrefs.edit();
|
||||
if (blacklist.isEmpty()) {
|
||||
editor.putStringSet("package_blacklist", null);
|
||||
editor.putStringSet(GBPrefs.PACKAGE_BLACKLIST, null);
|
||||
} else {
|
||||
editor.putStringSet("package_blacklist", blacklist);
|
||||
editor.putStringSet(GBPrefs.PACKAGE_BLACKLIST, blacklist);
|
||||
}
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public static void addToBlacklist(String packageName) {
|
||||
if (!blacklist.contains(packageName)) {
|
||||
blacklist.add(packageName);
|
||||
if (blacklist.add(packageName)) {
|
||||
saveBlackList();
|
||||
}
|
||||
}
|
||||
@ -433,7 +470,7 @@ public class GBApplication extends Application {
|
||||
public static int getTextColor(Context context) {
|
||||
TypedValue typedValue = new TypedValue();
|
||||
Resources.Theme theme = context.getTheme();
|
||||
theme.resolveAttribute(android.R.attr.textColorPrimary, typedValue, true);
|
||||
theme.resolveAttribute(R.attr.textColorPrimary, typedValue, true);
|
||||
return typedValue.data;
|
||||
}
|
||||
|
||||
@ -455,4 +492,8 @@ public class GBApplication extends Application {
|
||||
public DeviceManager getDeviceManager() {
|
||||
return deviceManager;
|
||||
}
|
||||
|
||||
public static GBApplication app() {
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,10 @@ package nodomain.freeyourgadget.gadgetbridge;
|
||||
* Some more or less useful utility methods to aid local (non-device) testing.
|
||||
*/
|
||||
public class GBEnvironment {
|
||||
// DO NOT USE A LOGGER HERE. Will break LoggingTest!
|
||||
// private static final Logger LOG = LoggerFactory.getLogger(GBEnvironment.class);
|
||||
|
||||
private static GBEnvironment environment;
|
||||
private boolean localTest;
|
||||
private boolean deviceTest;
|
||||
|
||||
@ -41,4 +45,15 @@ public class GBEnvironment {
|
||||
return localTest;
|
||||
}
|
||||
|
||||
public static synchronized GBEnvironment env() {
|
||||
return environment;
|
||||
}
|
||||
|
||||
static synchronized boolean isEnvironmentSetup() {
|
||||
return environment != null;
|
||||
}
|
||||
|
||||
public synchronized static void setupEnvironment(GBEnvironment env) {
|
||||
environment = env;
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBOpenHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
|
||||
@ -36,7 +35,7 @@ public class LockHandler implements DBHandler {
|
||||
public LockHandler() {
|
||||
}
|
||||
|
||||
public void init(DaoMaster daoMaster, DBOpenHelper helper) {
|
||||
public void init(DaoMaster daoMaster, DaoMaster.OpenHelper helper) {
|
||||
if (isValid()) {
|
||||
throw new IllegalStateException("DB must be closed before initializing it again");
|
||||
}
|
||||
@ -82,7 +81,7 @@ public class LockHandler implements DBHandler {
|
||||
throw new IllegalStateException("session must be null");
|
||||
}
|
||||
// this will create completely new db instances and in turn update this handler through #init()
|
||||
GBApplication.setupDatabase(GBApplication.getContext());
|
||||
GBApplication.app().setupDatabase();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti, walkjivefly
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -18,7 +19,6 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
|
||||
/**
|
||||
* Abstract base class for fragments. Provides hooks that are called when
|
||||
@ -28,7 +28,7 @@ import android.support.v4.app.FragmentActivity;
|
||||
* @see AbstractGBFragmentActivity
|
||||
*/
|
||||
public abstract class AbstractGBFragment extends Fragment {
|
||||
private boolean mVisibleInactivity;
|
||||
private boolean mVisibleInActivity;
|
||||
|
||||
/**
|
||||
* Called when this fragment has been fully scrolled into the activity.
|
||||
@ -37,7 +37,6 @@ public abstract class AbstractGBFragment extends Fragment {
|
||||
* @see #onMadeInvisibleInActivity()
|
||||
*/
|
||||
protected void onMadeVisibleInActivity() {
|
||||
updateActivityTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,7 +46,7 @@ public abstract class AbstractGBFragment extends Fragment {
|
||||
* @see #onMadeVisibleInActivity()
|
||||
*/
|
||||
protected void onMadeInvisibleInActivity() {
|
||||
mVisibleInactivity = false;
|
||||
mVisibleInActivity = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -55,16 +54,7 @@ public abstract class AbstractGBFragment extends Fragment {
|
||||
* activity, not taking into account whether the screen is enabled at all.
|
||||
*/
|
||||
public boolean isVisibleInActivity() {
|
||||
return mVisibleInactivity;
|
||||
}
|
||||
|
||||
protected void updateActivityTitle() {
|
||||
FragmentActivity activity = getActivity();
|
||||
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
|
||||
if (getTitle() != null) {
|
||||
activity.setTitle(getTitle());
|
||||
}
|
||||
}
|
||||
return mVisibleInActivity;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@ -76,7 +66,7 @@ public abstract class AbstractGBFragment extends Fragment {
|
||||
* @hide
|
||||
*/
|
||||
public void onMadeVisibleInActivityInternal() {
|
||||
mVisibleInactivity = true;
|
||||
mVisibleInActivity = true;
|
||||
if (isVisible()) {
|
||||
onMadeVisibleInActivity();
|
||||
}
|
||||
|
@ -21,8 +21,7 @@ import android.os.Bundle;
|
||||
import android.text.format.DateFormat;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.TextView;
|
||||
import android.widget.CheckedTextView;
|
||||
import android.widget.TimePicker;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@ -36,16 +35,15 @@ public class AlarmDetails extends GBActivity {
|
||||
|
||||
private GBAlarm alarm;
|
||||
private TimePicker timePicker;
|
||||
private CheckBox cbSmartWakeup;
|
||||
private CheckBox cbMonday;
|
||||
private CheckBox cbTuesday;
|
||||
private CheckBox cbWednesday;
|
||||
private CheckBox cbThursday;
|
||||
private CheckBox cbFriday;
|
||||
private CheckBox cbSaturday;
|
||||
private CheckBox cbSunday;
|
||||
private CheckedTextView cbSmartWakeup;
|
||||
private CheckedTextView cbMonday;
|
||||
private CheckedTextView cbTuesday;
|
||||
private CheckedTextView cbWednesday;
|
||||
private CheckedTextView cbThursday;
|
||||
private CheckedTextView cbFriday;
|
||||
private CheckedTextView cbSaturday;
|
||||
private CheckedTextView cbSunday;
|
||||
private GBDevice device;
|
||||
private TextView smartAlarmLabel;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -56,15 +54,56 @@ public class AlarmDetails extends GBActivity {
|
||||
device = getIntent().getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
|
||||
timePicker = (TimePicker) findViewById(R.id.alarm_time_picker);
|
||||
smartAlarmLabel = (TextView) findViewById(R.id.alarm_label_smart_wakeup);
|
||||
cbSmartWakeup = (CheckBox) findViewById(R.id.alarm_cb_smart_wakeup);
|
||||
cbMonday = (CheckBox) findViewById(R.id.alarm_cb_mon);
|
||||
cbTuesday = (CheckBox) findViewById(R.id.alarm_cb_tue);
|
||||
cbWednesday = (CheckBox) findViewById(R.id.alarm_cb_wed);
|
||||
cbThursday = (CheckBox) findViewById(R.id.alarm_cb_thu);
|
||||
cbFriday = (CheckBox) findViewById(R.id.alarm_cb_fri);
|
||||
cbSaturday = (CheckBox) findViewById(R.id.alarm_cb_sat);
|
||||
cbSunday = (CheckBox) findViewById(R.id.alarm_cb_sun);
|
||||
cbSmartWakeup = (CheckedTextView) findViewById(R.id.alarm_cb_smart_wakeup);
|
||||
cbMonday = (CheckedTextView) findViewById(R.id.alarm_cb_monday);
|
||||
cbTuesday = (CheckedTextView) findViewById(R.id.alarm_cb_tuesday);
|
||||
cbWednesday = (CheckedTextView) findViewById(R.id.alarm_cb_wednesday);
|
||||
cbThursday = (CheckedTextView) findViewById(R.id.alarm_cb_thursday);
|
||||
cbFriday = (CheckedTextView) findViewById(R.id.alarm_cb_friday);
|
||||
cbSaturday = (CheckedTextView) findViewById(R.id.alarm_cb_saturday);
|
||||
cbSunday = (CheckedTextView) findViewById(R.id.alarm_cb_sunday);
|
||||
|
||||
|
||||
cbSmartWakeup.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
((CheckedTextView) v).toggle();
|
||||
}
|
||||
});
|
||||
cbMonday.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
((CheckedTextView) v).toggle();
|
||||
}
|
||||
});
|
||||
cbTuesday.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
((CheckedTextView) v).toggle();
|
||||
}
|
||||
});
|
||||
cbWednesday.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
((CheckedTextView) v).toggle();
|
||||
}
|
||||
});
|
||||
cbThursday.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
((CheckedTextView) v).toggle();
|
||||
}
|
||||
});
|
||||
cbFriday.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
((CheckedTextView) v).toggle();
|
||||
}
|
||||
});
|
||||
cbSaturday.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
((CheckedTextView) v).toggle();
|
||||
}
|
||||
});
|
||||
cbSunday.setOnClickListener(new View.OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
((CheckedTextView) v).toggle();
|
||||
}
|
||||
});
|
||||
|
||||
timePicker.setIs24HourView(DateFormat.is24HourFormat(GBApplication.getContext()));
|
||||
timePicker.setCurrentHour(alarm.getHour());
|
||||
@ -73,7 +112,6 @@ public class AlarmDetails extends GBActivity {
|
||||
cbSmartWakeup.setChecked(alarm.isSmartWakeup());
|
||||
int smartAlarmVisibility = supportsSmartWakeup() ? View.VISIBLE : View.GONE;
|
||||
cbSmartWakeup.setVisibility(smartAlarmVisibility);
|
||||
smartAlarmLabel.setVisibility(smartAlarmVisibility);
|
||||
|
||||
cbMonday.setChecked(alarm.getRepetition(GBAlarm.ALARM_MON));
|
||||
cbTuesday.setChecked(alarm.getRepetition(GBAlarm.ALARM_TUE));
|
||||
|
@ -21,37 +21,27 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.AppBlacklistAdapter;
|
||||
|
||||
|
||||
public class AppBlacklistActivity extends GBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AppBlacklistActivity.class);
|
||||
|
||||
private AppBlacklistAdapter appBlacklistAdapter;
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
@ -62,83 +52,35 @@ public class AppBlacklistActivity extends GBActivity {
|
||||
}
|
||||
};
|
||||
|
||||
private IdentityHashMap<ApplicationInfo, String> nameMap;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_appblacklist);
|
||||
RecyclerView appListView = (RecyclerView) findViewById(R.id.appListView);
|
||||
appListView.setLayoutManager(new LinearLayoutManager(this));
|
||||
|
||||
final PackageManager pm = getPackageManager();
|
||||
appBlacklistAdapter = new AppBlacklistAdapter(R.layout.item_app_blacklist, this);
|
||||
|
||||
final List<ApplicationInfo> packageList = pm.getInstalledApplications(PackageManager.GET_META_DATA);
|
||||
ListView appListView = (ListView) findViewById(R.id.appListView);
|
||||
appListView.setAdapter(appBlacklistAdapter);
|
||||
|
||||
// sort the package list by label and blacklist status
|
||||
nameMap = new IdentityHashMap<>(packageList.size());
|
||||
for (ApplicationInfo ai : packageList) {
|
||||
CharSequence name = pm.getApplicationLabel(ai);
|
||||
if (name == null) {
|
||||
name = ai.packageName;
|
||||
}
|
||||
if (GBApplication.blacklist.contains(ai.packageName)) {
|
||||
// sort blacklisted first by prefixing with a '!'
|
||||
name = "!" + name;
|
||||
}
|
||||
nameMap.put(ai, name.toString());
|
||||
}
|
||||
|
||||
Collections.sort(packageList, new Comparator<ApplicationInfo>() {
|
||||
SearchView searchView = (SearchView) findViewById(R.id.appListViewSearch);
|
||||
searchView.setIconifiedByDefault(false);
|
||||
searchView.setIconified(false);
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public int compare(ApplicationInfo ai1, ApplicationInfo ai2) {
|
||||
final String s1 = nameMap.get(ai1);
|
||||
final String s2 = nameMap.get(ai2);
|
||||
return s1.compareTo(s2);
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
final ArrayAdapter<ApplicationInfo> adapter = new ArrayAdapter<ApplicationInfo>(this, R.layout.item_with_checkbox, packageList) {
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent) {
|
||||
if (view == null) {
|
||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
view = inflater.inflate(R.layout.item_with_checkbox, parent, false);
|
||||
}
|
||||
|
||||
ApplicationInfo appInfo = packageList.get(position);
|
||||
TextView deviceAppVersionAuthorLabel = (TextView) view.findViewById(R.id.item_details);
|
||||
TextView deviceAppNameLabel = (TextView) view.findViewById(R.id.item_name);
|
||||
ImageView deviceImageView = (ImageView) view.findViewById(R.id.item_image);
|
||||
CheckBox checkbox = (CheckBox) view.findViewById(R.id.item_checkbox);
|
||||
|
||||
deviceAppVersionAuthorLabel.setText(appInfo.packageName);
|
||||
deviceAppNameLabel.setText(nameMap.get(appInfo));
|
||||
deviceImageView.setImageDrawable(appInfo.loadIcon(pm));
|
||||
|
||||
checkbox.setChecked(GBApplication.blacklist.contains(appInfo.packageName));
|
||||
|
||||
return view;
|
||||
}
|
||||
};
|
||||
appListView.setAdapter(adapter);
|
||||
|
||||
appListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView parent, View v, int position, long id) {
|
||||
String packageName = packageList.get(position).packageName;
|
||||
CheckBox checkBox = ((CheckBox) v.findViewById(R.id.item_checkbox));
|
||||
checkBox.toggle();
|
||||
if (checkBox.isChecked()) {
|
||||
GBApplication.addToBlacklist(packageName);
|
||||
} else {
|
||||
GBApplication.removeFromBlacklist(packageName);
|
||||
}
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
appBlacklistAdapter.getFilter().filter(newText);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(GBApplication.ACTION_QUIT);
|
||||
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
|
||||
}
|
||||
|
||||
|
@ -19,8 +19,9 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.ListView;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
@ -64,8 +65,10 @@ public class ConfigureAlarms extends GBActivity {
|
||||
|
||||
mGBAlarmListAdapter = new GBAlarmListAdapter(this, preferencesAlarmListSet);
|
||||
|
||||
ListView listView = (ListView) findViewById(R.id.alarm_list);
|
||||
listView.setAdapter(mGBAlarmListAdapter);
|
||||
RecyclerView alarmsRecyclerView = (RecyclerView) findViewById(R.id.alarm_list);
|
||||
alarmsRecyclerView.setHasFixedSize(true);
|
||||
alarmsRecyclerView.setLayoutManager(new LinearLayoutManager(this));
|
||||
alarmsRecyclerView.setAdapter(mGBAlarmListAdapter);
|
||||
updateAlarmsFromPrefs();
|
||||
}
|
||||
|
||||
|
@ -250,15 +250,6 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
|
||||
private void refreshPairedDevices() {
|
||||
List<GBDevice> deviceList = deviceManager.getDevices();
|
||||
GBDevice connectedDevice = null;
|
||||
|
||||
for (GBDevice device : deviceList) {
|
||||
if (device.isConnected() || device.isConnecting()) {
|
||||
connectedDevice = device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceList.isEmpty()) {
|
||||
background.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
/* Copyright (C) 2016-2017 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -20,8 +20,10 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
@ -33,6 +35,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
@ -40,10 +43,13 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.ImportExportSharedPreferences;
|
||||
|
||||
|
||||
public class DbManagementActivity extends GBActivity {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DbManagementActivity.class);
|
||||
private static SharedPreferences sharedPrefs;
|
||||
private ImportExportSharedPreferences shared_file = new ImportExportSharedPreferences();
|
||||
|
||||
private Button exportDBButton;
|
||||
private Button importDBButton;
|
||||
@ -95,6 +101,8 @@ public class DbManagementActivity extends GBActivity {
|
||||
deleteActivityDatabase();
|
||||
}
|
||||
});
|
||||
|
||||
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
}
|
||||
|
||||
private boolean hasOldActivityDatabase() {
|
||||
@ -110,8 +118,33 @@ public class DbManagementActivity extends GBActivity {
|
||||
return getString(R.string.dbmanagementactivvity_cannot_access_export_path);
|
||||
}
|
||||
|
||||
private void exportShared() {
|
||||
// BEGIN EXAMPLE
|
||||
File myPath = null;
|
||||
try {
|
||||
myPath = FileUtils.getExternalFilesDir();
|
||||
File myFile = new File(myPath, "Export_preference");
|
||||
shared_file.exportToFile(sharedPrefs,myFile,null);
|
||||
} catch (IOException ex) {
|
||||
GB.toast(this, getString(R.string.dbmanagementactivity_error_exporting_shared, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void importShared() {
|
||||
// BEGIN EXAMPLE
|
||||
File myPath = null;
|
||||
try {
|
||||
myPath = FileUtils.getExternalFilesDir();
|
||||
File myFile = new File(myPath, "Export_preference");
|
||||
shared_file.importFromFile(sharedPrefs,myFile );
|
||||
} catch (Exception ex) {
|
||||
GB.toast(DbManagementActivity.this, getString(R.string.dbmanagementactivity_error_importing_db, ex.getMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void exportDB() {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
exportShared();
|
||||
DBHelper helper = new DBHelper(this);
|
||||
File dir = FileUtils.getExternalFilesDir();
|
||||
File destFile = helper.exportDB(dbHandler, dir);
|
||||
@ -130,6 +163,7 @@ public class DbManagementActivity extends GBActivity {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
importShared();
|
||||
DBHelper helper = new DBHelper(DbManagementActivity.this);
|
||||
File dir = FileUtils.getExternalFilesDir();
|
||||
SQLiteOpenHelper sqLiteOpenHelper = dbHandler.getHelper();
|
||||
|
@ -20,6 +20,7 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
import android.Manifest;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothManager;
|
||||
@ -30,6 +31,7 @@ import android.bluetooth.le.ScanResult;
|
||||
import android.bluetooth.le.ScanSettings;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
@ -123,7 +125,7 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC
|
||||
}
|
||||
case BluetoothDevice.ACTION_BOND_STATE_CHANGED: {
|
||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
if (device != null && device.getAddress().equals(bondingAddress)) {
|
||||
if (device != null && bondingDevice != null && device.getAddress().equals(bondingDevice.getMacAddress())) {
|
||||
int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, BluetoothDevice.BOND_NONE);
|
||||
if (bondState == BluetoothDevice.BOND_BONDED) {
|
||||
handleDeviceBonded();
|
||||
@ -134,11 +136,57 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC
|
||||
}
|
||||
};
|
||||
|
||||
private void handleDeviceBonded() {
|
||||
GB.toast(DiscoveryActivity.this, "Successfully bonded with: " + bondingAddress, Toast.LENGTH_SHORT, GB.INFO);
|
||||
private void connectAndFinish(GBDevice device) {
|
||||
GB.toast(DiscoveryActivity.this, getString(R.string.discovery_trying_to_connect_to, device.getName()), Toast.LENGTH_SHORT, GB.INFO);
|
||||
GBApplication.deviceService().connect(device, true);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void createBond(final GBDeviceCandidate deviceCandidate, int bondingStyle) {
|
||||
if (bondingStyle == DeviceCoordinator.BONDING_STYLE_NONE) {
|
||||
return;
|
||||
}
|
||||
if (bondingStyle == DeviceCoordinator.BONDING_STYLE_ASK) {
|
||||
new AlertDialog.Builder(this)
|
||||
.setCancelable(true)
|
||||
.setTitle(DiscoveryActivity.this.getString(R.string.discovery_pair_title, deviceCandidate.getName()))
|
||||
.setMessage(DiscoveryActivity.this.getString(R.string.discovery_pair_question))
|
||||
.setPositiveButton(DiscoveryActivity.this.getString(R.string.discovery_yes_pair), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
doCreatePair(deviceCandidate);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.discovery_dont_pair, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate);
|
||||
connectAndFinish(device);
|
||||
}
|
||||
})
|
||||
.show();
|
||||
} else {
|
||||
doCreatePair(deviceCandidate);
|
||||
}
|
||||
}
|
||||
|
||||
private void doCreatePair(GBDeviceCandidate deviceCandidate) {
|
||||
GB.toast(DiscoveryActivity.this, getString(R.string.discovery_attempting_to_pair, deviceCandidate.getName()), Toast.LENGTH_SHORT, GB.INFO);
|
||||
if (deviceCandidate.getDevice().createBond()) {
|
||||
// async, wait for bonding event to finish this activity
|
||||
LOG.info("Bonding in progress...");
|
||||
bondingDevice = deviceCandidate;
|
||||
} else {
|
||||
GB.toast(DiscoveryActivity.this, getString(R.string.discovery_bonding_failed_immediately, deviceCandidate.getName()), Toast.LENGTH_SHORT, GB.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleDeviceBonded() {
|
||||
GB.toast(DiscoveryActivity.this, getString(R.string.discovery_successfully_bonded, bondingDevice.getName()), Toast.LENGTH_SHORT, GB.INFO);
|
||||
GBDevice device = DeviceHelper.getInstance().toSupportedDevice(bondingDevice);
|
||||
connectAndFinish(device);
|
||||
}
|
||||
|
||||
private final BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
|
||||
@Override
|
||||
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
|
||||
@ -203,7 +251,7 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC
|
||||
private DeviceCandidateAdapter cadidateListAdapter;
|
||||
private Button startButton;
|
||||
private Scanning isScanning = Scanning.SCANNING_OFF;
|
||||
private String bondingAddress;
|
||||
private GBDeviceCandidate bondingDevice;
|
||||
|
||||
private enum Scanning {
|
||||
SCANNING_BT,
|
||||
@ -358,7 +406,7 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC
|
||||
}
|
||||
} else {
|
||||
discoveryFinished();
|
||||
Toast.makeText(this, "Enable Bluetooth to discover devices.", Toast.LENGTH_LONG).show();
|
||||
GB.toast(DiscoveryActivity.this, getString(R.string.discovery_enable_bluetooth), Toast.LENGTH_SHORT, GB.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@ -535,19 +583,24 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC
|
||||
intent.putExtra(DeviceCoordinator.EXTRA_DEVICE_CANDIDATE, deviceCandidate);
|
||||
startActivity(intent);
|
||||
} else {
|
||||
GBDevice device = DeviceHelper.getInstance().toSupportedDevice(deviceCandidate);
|
||||
int bondingStyle = coordinator.getBondingStyle(device);
|
||||
if (bondingStyle == DeviceCoordinator.BONDING_STYLE_NONE) {
|
||||
LOG.info("No bonding needed, according to coordinator, so connecting right away");
|
||||
connectAndFinish(device);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
BluetoothDevice btDevice = adapter.getRemoteDevice(deviceCandidate.getMacAddress());
|
||||
switch (btDevice.getBondState()) {
|
||||
case BluetoothDevice.BOND_NONE: {
|
||||
if (btDevice.createBond()) {
|
||||
// async, wait for bonding event to finish this activity
|
||||
bondingAddress = btDevice.getAddress();
|
||||
}
|
||||
createBond(deviceCandidate, bondingStyle);
|
||||
break;
|
||||
}
|
||||
case BluetoothDevice.BOND_BONDING:
|
||||
// async, wait for bonding event to finish this activity
|
||||
bondingAddress = btDevice.getAddress();
|
||||
bondingDevice = deviceCandidate;
|
||||
break;
|
||||
case BluetoothDevice.BOND_BONDED:
|
||||
handleDeviceBonded();
|
||||
|
@ -24,6 +24,7 @@ import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
@ -259,11 +260,23 @@ public abstract class AbstractAppManagerFragment extends Fragment {
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
|
||||
final FloatingActionButton appListFab = ((FloatingActionButton) getActivity().findViewById(R.id.fab));
|
||||
View rootView = inflater.inflate(R.layout.activity_appmanager, container, false);
|
||||
|
||||
RecyclerView appListView = (RecyclerView) (rootView.findViewById(R.id.appListView));
|
||||
|
||||
appListView.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||
if (dy > 0) {
|
||||
appListFab.hide();
|
||||
} else if (dy < 0) {
|
||||
appListFab.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
appListView.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
mGBDeviceAppAdapter = new GBDeviceAppAdapter(appList, R.layout.item_with_details_and_drag_handle, this);
|
||||
mGBDeviceAppAdapter = new GBDeviceAppAdapter(appList, R.layout.item_pebble_watchapp, this);
|
||||
appListView.setAdapter(mGBDeviceAppAdapter);
|
||||
|
||||
ItemTouchHelper.Callback appItemTouchHelperCallback = new AppItemTouchHelperCallback(mGBDeviceAppAdapter);
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Daniele Gobbetti
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -176,14 +177,11 @@ public class AppManagerActivity extends AbstractGBFragmentActivity {
|
||||
|
||||
|
||||
static synchronized void rewriteAppOrderFile(String filename, List<UUID> uuids) {
|
||||
try {
|
||||
FileWriter fileWriter = new FileWriter(FileUtils.getExternalFilesDir() + "/" + filename);
|
||||
BufferedWriter out = new BufferedWriter(fileWriter);
|
||||
try (BufferedWriter out = new BufferedWriter(new FileWriter(FileUtils.getExternalFilesDir() + "/" + filename))) {
|
||||
for (UUID uuid : uuids) {
|
||||
out.write(uuid.toString());
|
||||
out.newLine();
|
||||
}
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
LOG.warn("can't write app order to file!");
|
||||
}
|
||||
@ -199,9 +197,7 @@ public class AppManagerActivity extends AbstractGBFragmentActivity {
|
||||
|
||||
static synchronized ArrayList<UUID> getUuidsFromFile(String filename) {
|
||||
ArrayList<UUID> uuids = new ArrayList<>();
|
||||
try {
|
||||
FileReader fileReader = new FileReader(FileUtils.getExternalFilesDir() + "/" + filename);
|
||||
BufferedReader in = new BufferedReader(fileReader);
|
||||
try (BufferedReader in = new BufferedReader(new FileReader(FileUtils.getExternalFilesDir() + "/" + filename))) {
|
||||
String line;
|
||||
while ((line = in.readLine()) != null) {
|
||||
uuids.add(UUID.fromString(line));
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2017 0nse, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti
|
||||
Daniele Gobbetti, walkjivefly
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -93,7 +93,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
* shift the date by one day.
|
||||
*/
|
||||
public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
protected final int ANIM_TIME = 350;
|
||||
protected final int ANIM_TIME = 250;
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(AbstractChartFragment.class);
|
||||
|
||||
@ -154,11 +154,11 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
mIntentFilterActions = new HashSet<>();
|
||||
if (intentFilterActions != null) {
|
||||
mIntentFilterActions.addAll(Arrays.asList(intentFilterActions));
|
||||
}
|
||||
mIntentFilterActions.add(ChartsHost.DATE_NEXT);
|
||||
mIntentFilterActions.add(ChartsHost.DATE_PREV);
|
||||
mIntentFilterActions.add(ChartsHost.REFRESH);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
@ -182,9 +182,9 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
HEARTRATE_FILL_COLOR = ContextCompat.getColor(getContext(), R.color.chart_heartrate_fill);
|
||||
getContext().getTheme().resolveAttribute(R.attr.chart_activity, runningColor, true);
|
||||
AK_ACTIVITY_COLOR = runningColor.data;
|
||||
getContext().getTheme().resolveAttribute(R.attr.chart_light_sleep, runningColor, true);
|
||||
AK_DEEP_SLEEP_COLOR = runningColor.data;
|
||||
getContext().getTheme().resolveAttribute(R.attr.chart_deep_sleep, runningColor, true);
|
||||
AK_DEEP_SLEEP_COLOR = runningColor.data;
|
||||
getContext().getTheme().resolveAttribute(R.attr.chart_light_sleep, runningColor, true);
|
||||
AK_LIGHT_SLEEP_COLOR = runningColor.data;
|
||||
getContext().getTheme().resolveAttribute(R.attr.chart_not_worn, runningColor, true);
|
||||
AK_NOT_WORN_COLOR = runningColor.data;
|
||||
@ -234,7 +234,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
}
|
||||
|
||||
protected void showDateBar(boolean show) {
|
||||
getChartsHost().getDateBar().setVisibility(show ? View.VISIBLE : View.GONE);
|
||||
getChartsHost().getDateBar().setVisibility(show ? View.VISIBLE : View.INVISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2015-2017 0nse, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
/* Copyright (C) 2015-2017 0nse, Alberto, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
@ -25,7 +25,6 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.github.mikephil.charting.charts.BarChart;
|
||||
import com.github.mikephil.charting.charts.Chart;
|
||||
import com.github.mikephil.charting.charts.PieChart;
|
||||
import com.github.mikephil.charting.components.LimitLine;
|
||||
import com.github.mikephil.charting.components.XAxis;
|
||||
@ -81,13 +80,12 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
protected void updateChartsnUIThread(ChartsData chartsData) {
|
||||
MyChartsData mcd = (MyChartsData) chartsData;
|
||||
|
||||
// setupLegend(mWeekChart);
|
||||
setupLegend(mWeekChart);
|
||||
mTodayPieChart.setCenterText(mcd.getDayData().centerText);
|
||||
mTodayPieChart.setData(mcd.getDayData().data);
|
||||
|
||||
mWeekChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317
|
||||
mWeekChart.setData(mcd.getWeekBeforeData().getData());
|
||||
mWeekChart.getLegend().setEnabled(false);
|
||||
mWeekChart.getXAxis().setValueFormatter(mcd.getWeekBeforeData().getXValueFormatter());
|
||||
}
|
||||
|
||||
@ -117,6 +115,7 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
|
||||
BarData barData = new BarData(set);
|
||||
barData.setValueTextColor(Color.GRAY); //prevent tearing other graph elements with the black text. Another approach would be to hide the values cmpletely with data.setDrawValues(false);
|
||||
barData.setValueTextSize(10f);
|
||||
|
||||
LimitLine target = new LimitLine(mTargetValue);
|
||||
barChart.getAxisLeft().removeAllLimitLines();
|
||||
@ -133,23 +132,35 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
|
||||
ActivityAmounts amounts = getActivityAmountsForDay(db, day, device);
|
||||
float totalValues[] = getTotalsForActivityAmounts(amounts);
|
||||
String[] pieLabels = getPieLabels();
|
||||
float totalValue = 0;
|
||||
for (float value : totalValues) {
|
||||
for (int i = 0; i < totalValues.length; i++) {
|
||||
float value = totalValues[i];
|
||||
totalValue += value;
|
||||
entries.add(new PieEntry(value));
|
||||
entries.add(new PieEntry(value, pieLabels[i]));
|
||||
}
|
||||
|
||||
set.setValueFormatter(getPieValueFormatter());
|
||||
set.setColors(getColors());
|
||||
|
||||
if (totalValues.length < 2) {
|
||||
if (totalValue < mTargetValue) {
|
||||
entries.add(new PieEntry((mTargetValue - totalValue)));
|
||||
set.addColor(Color.GRAY);
|
||||
}
|
||||
}
|
||||
|
||||
data.setDataSet(set);
|
||||
//this hides the values (numeric) added to the set. These would be shown aside the strings set with addXValue above
|
||||
|
||||
if (totalValues.length < 2) {
|
||||
data.setDrawValues(false);
|
||||
}
|
||||
else {
|
||||
set.setXValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
|
||||
set.setYValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
|
||||
set.setValueTextColor(DESCRIPTION_COLOR);
|
||||
set.setValueTextSize(13f);
|
||||
set.setValueFormatter(getPieValueFormatter());
|
||||
}
|
||||
|
||||
return new DayData(data, formatPieValue((int) totalValue));
|
||||
}
|
||||
@ -181,11 +192,11 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
private void setupTodayPieChart() {
|
||||
mTodayPieChart.setBackgroundColor(BACKGROUND_COLOR);
|
||||
mTodayPieChart.getDescription().setTextColor(DESCRIPTION_COLOR);
|
||||
mTodayPieChart.setEntryLabelColor(DESCRIPTION_COLOR);
|
||||
mTodayPieChart.getDescription().setText(getPieDescription(mTargetValue));
|
||||
// mTodayPieChart.setNoDataTextDescription("");
|
||||
mTodayPieChart.setNoDataText("");
|
||||
mTodayPieChart.getLegend().setEnabled(false);
|
||||
// setupLegend(mTodayPieChart);
|
||||
}
|
||||
|
||||
private void setupWeekChart() {
|
||||
@ -222,16 +233,6 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
yAxisRight.setTextColor(CHART_TEXT_COLOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupLegend(Chart chart) {
|
||||
// List<Integer> legendColors = new ArrayList<>(1);
|
||||
// List<String> legendLabels = new ArrayList<>(1);
|
||||
// legendColors.add(akActivity.color);
|
||||
// legendLabels.add(getContext().getString(R.string.chart_steps));
|
||||
// chart.getLegend().setCustom(legendColors, legendLabels);
|
||||
// chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
|
||||
}
|
||||
|
||||
private List<? extends ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, int offsetHours, GBDevice device) {
|
||||
int startTs;
|
||||
int endTs;
|
||||
@ -312,6 +313,8 @@ public abstract class AbstractWeekChartFragment extends AbstractChartFragment {
|
||||
|
||||
abstract String formatPieValue(int value);
|
||||
|
||||
abstract String[] getPieLabels();
|
||||
|
||||
abstract IValueFormatter getPieValueFormatter();
|
||||
|
||||
abstract IValueFormatter getBarValueFormatter();
|
||||
|
@ -27,7 +27,6 @@ import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.support.v4.app.FragmentStatePagerAdapter;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v4.view.PagerTabStrip;
|
||||
import android.support.v4.view.ViewPager;
|
||||
import android.support.v4.widget.SwipeRefreshLayout;
|
||||
import android.view.Menu;
|
||||
@ -67,7 +66,6 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
private Date mStartDate;
|
||||
private Date mEndDate;
|
||||
private SwipeRefreshLayout swipeLayout;
|
||||
private PagerTabStrip mPagerTabStrip;
|
||||
private ViewPager viewPager;
|
||||
|
||||
LimitedQueue mActivityAmountCache = new LimitedQueue(60);
|
||||
@ -200,7 +198,6 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts
|
||||
handleNextButtonClicked();
|
||||
}
|
||||
});
|
||||
mPagerTabStrip = (PagerTabStrip) findViewById(R.id.charts_pagerTabStrip);
|
||||
|
||||
LinearLayout mainLayout = (LinearLayout) findViewById(R.id.charts_main_layout);
|
||||
}
|
||||
|
@ -105,6 +105,10 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
}
|
||||
});
|
||||
set.setColors(colors);
|
||||
set.setValueTextColor(DESCRIPTION_COLOR);
|
||||
set.setValueTextSize(13f);
|
||||
set.setXValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
|
||||
set.setYValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE);
|
||||
data.setDataSet(set);
|
||||
|
||||
//setupLegend(pieChart);
|
||||
@ -162,6 +166,7 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
private void setupSleepAmountChart() {
|
||||
mSleepAmountChart.setBackgroundColor(BACKGROUND_COLOR);
|
||||
mSleepAmountChart.getDescription().setTextColor(DESCRIPTION_COLOR);
|
||||
mSleepAmountChart.setEntryLabelColor(DESCRIPTION_COLOR);
|
||||
mSleepAmountChart.getDescription().setText("");
|
||||
// mSleepAmountChart.getDescription().setNoDataTextDescription("");
|
||||
mSleepAmountChart.setNoDataText("");
|
||||
|
@ -16,12 +16,16 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||
|
||||
import com.github.mikephil.charting.charts.Chart;
|
||||
import com.github.mikephil.charting.components.AxisBase;
|
||||
import com.github.mikephil.charting.components.LegendEntry;
|
||||
import com.github.mikephil.charting.data.Entry;
|
||||
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
|
||||
import com.github.mikephil.charting.formatter.IValueFormatter;
|
||||
import com.github.mikephil.charting.utils.ViewPortHandler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@ -40,7 +44,7 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
||||
|
||||
@Override
|
||||
String getPieDescription(int targetValue) {
|
||||
return getString(R.string.weeksleepchart_today_sleep_description, DateTimeUtils.minutesToHHMM(targetValue));
|
||||
return getString(R.string.weeksleepchart_today_sleep_description, DateTimeUtils.formatDurationHoursMinutes(targetValue, TimeUnit.MINUTES));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -72,6 +76,11 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
||||
return DateTimeUtils.formatDurationHoursMinutes((long) value, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
String[] getPieLabels() {
|
||||
return new String[]{getString(R.string.abstract_chart_fragment_kind_deep_sleep), getString(R.string.abstract_chart_fragment_kind_light_sleep)};
|
||||
}
|
||||
|
||||
@Override
|
||||
IValueFormatter getPieValueFormatter() {
|
||||
return new IValueFormatter() {
|
||||
@ -106,4 +115,22 @@ public class WeekSleepChartFragment extends AbstractWeekChartFragment {
|
||||
int[] getColors() {
|
||||
return new int[]{akDeepSleep.color, akLightSleep.color};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupLegend(Chart chart) {
|
||||
List<LegendEntry> legendEntries = new ArrayList<>(2);
|
||||
|
||||
LegendEntry lightSleepEntry = new LegendEntry();
|
||||
lightSleepEntry.label = akLightSleep.label;
|
||||
lightSleepEntry.formColor = akLightSleep.color;
|
||||
legendEntries.add(lightSleepEntry);
|
||||
|
||||
LegendEntry deepSleepEntry = new LegendEntry();
|
||||
deepSleepEntry.label = akDeepSleep.label;
|
||||
deepSleepEntry.formColor = akDeepSleep.color;
|
||||
legendEntries.add(deepSleepEntry);
|
||||
|
||||
chart.getLegend().setCustom(legendEntries);
|
||||
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
|
||||
|
||||
import com.github.mikephil.charting.charts.Chart;
|
||||
import com.github.mikephil.charting.formatter.IAxisValueFormatter;
|
||||
import com.github.mikephil.charting.formatter.IValueFormatter;
|
||||
|
||||
@ -62,6 +63,11 @@ public class WeekStepsChartFragment extends AbstractWeekChartFragment {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
String[] getPieLabels() {
|
||||
return new String[]{""};
|
||||
}
|
||||
|
||||
@Override
|
||||
IValueFormatter getPieValueFormatter() {
|
||||
return null;
|
||||
@ -81,4 +87,10 @@ public class WeekStepsChartFragment extends AbstractWeekChartFragment {
|
||||
int[] getColors() {
|
||||
return new int[]{akActivity.color};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupLegend(Chart chart) {
|
||||
// no legend here, it is all about the steps here
|
||||
chart.getLegend().setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,189 @@
|
||||
/* Copyright (C) 2017 Carsten Pfeiffer, Daniele Gobbetti
|
||||
|
||||
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.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.Filter;
|
||||
import android.widget.Filterable;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public class AppBlacklistAdapter extends RecyclerView.Adapter<AppBlacklistAdapter.AppBLViewHolder> implements Filterable {
|
||||
|
||||
private List<ApplicationInfo> applicationInfoList;
|
||||
private final int mLayoutId;
|
||||
private final Context mContext;
|
||||
private final PackageManager mPm;
|
||||
private final IdentityHashMap<ApplicationInfo, String> mNameMap;
|
||||
|
||||
private ApplicationFilter applicationFilter;
|
||||
|
||||
public AppBlacklistAdapter(int layoutId, Context context) {
|
||||
mLayoutId = layoutId;
|
||||
mContext = context;
|
||||
mPm = context.getPackageManager();
|
||||
|
||||
applicationInfoList = mPm.getInstalledApplications(PackageManager.GET_META_DATA);
|
||||
|
||||
// sort the package list by label and blacklist status
|
||||
mNameMap = new IdentityHashMap<>(applicationInfoList.size());
|
||||
for (ApplicationInfo ai : applicationInfoList) {
|
||||
CharSequence name = mPm.getApplicationLabel(ai);
|
||||
if (name == null) {
|
||||
name = ai.packageName;
|
||||
}
|
||||
if (GBApplication.isBlacklisted(ai.packageName)) {
|
||||
// sort blacklisted first by prefixing with a '!'
|
||||
name = "!" + name;
|
||||
}
|
||||
mNameMap.put(ai, name.toString());
|
||||
}
|
||||
|
||||
Collections.sort(applicationInfoList, new Comparator<ApplicationInfo>() {
|
||||
@Override
|
||||
public int compare(ApplicationInfo ai1, ApplicationInfo ai2) {
|
||||
final String s1 = mNameMap.get(ai1);
|
||||
final String s2 = mNameMap.get(ai2);
|
||||
return s1.compareTo(s2);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public AppBlacklistAdapter.AppBLViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(mContext).inflate(mLayoutId, parent, false);
|
||||
return new AppBLViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(AppBlacklistAdapter.AppBLViewHolder holder, int position) {
|
||||
final ApplicationInfo appInfo = applicationInfoList.get(position);
|
||||
|
||||
holder.deviceAppVersionAuthorLabel.setText(appInfo.packageName);
|
||||
holder.deviceAppNameLabel.setText(mNameMap.get(appInfo));
|
||||
holder.deviceImageView.setImageDrawable(appInfo.loadIcon(mPm));
|
||||
|
||||
holder.checkbox.setChecked(GBApplication.isBlacklisted(appInfo.packageName));
|
||||
|
||||
holder.itemView.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
CheckBox checkBox = ((CheckBox) v.findViewById(R.id.item_checkbox));
|
||||
checkBox.toggle();
|
||||
if (checkBox.isChecked()) {
|
||||
GBApplication.addToBlacklist(appInfo.packageName);
|
||||
} else {
|
||||
GBApplication.removeFromBlacklist(appInfo.packageName);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return applicationInfoList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
if (applicationFilter == null)
|
||||
applicationFilter = new ApplicationFilter(this, applicationInfoList);
|
||||
return applicationFilter;
|
||||
}
|
||||
|
||||
public class AppBLViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
final CheckBox checkbox;
|
||||
final ImageView deviceImageView;
|
||||
final TextView deviceAppVersionAuthorLabel;
|
||||
final TextView deviceAppNameLabel;
|
||||
|
||||
AppBLViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
checkbox = (CheckBox) itemView.findViewById(R.id.item_checkbox);
|
||||
deviceImageView = (ImageView) itemView.findViewById(R.id.item_image);
|
||||
deviceAppVersionAuthorLabel = (TextView) itemView.findViewById(R.id.item_details);
|
||||
deviceAppNameLabel = (TextView) itemView.findViewById(R.id.item_name);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ApplicationFilter extends Filter {
|
||||
|
||||
private final AppBlacklistAdapter adapter;
|
||||
private final List<ApplicationInfo> originalList;
|
||||
private final List<ApplicationInfo> filteredList;
|
||||
|
||||
private ApplicationFilter(AppBlacklistAdapter adapter, List<ApplicationInfo> originalList) {
|
||||
super();
|
||||
this.originalList = new ArrayList<>(originalList);
|
||||
this.filteredList = new ArrayList<>();
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Filter.FilterResults performFiltering(CharSequence filter) {
|
||||
filteredList.clear();
|
||||
final Filter.FilterResults results = new Filter.FilterResults();
|
||||
|
||||
if (filter == null || filter.length() == 0)
|
||||
filteredList.addAll(originalList);
|
||||
else {
|
||||
final String filterPattern = filter.toString().toLowerCase().trim();
|
||||
|
||||
for (ApplicationInfo ai : originalList) {
|
||||
CharSequence name = mPm.getApplicationLabel(ai);
|
||||
if (name.toString().contains(filterPattern) ||
|
||||
(ai.packageName.contains(filterPattern))) {
|
||||
filteredList.add(ai);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results.values = filteredList;
|
||||
results.count = filteredList.size();
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void publishResults(CharSequence charSequence, Filter.FilterResults filterResults) {
|
||||
adapter.applicationInfoList.clear();
|
||||
adapter.applicationInfoList.addAll((List<ApplicationInfo>) filterResults.values);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -19,20 +19,21 @@ package nodomain.freeyourgadget.gadgetbridge.adapter;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.support.v7.widget.CardView;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckedTextView;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
|
||||
@ -41,22 +42,18 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||
/**
|
||||
* Adapter for displaying GBAlarm instances.
|
||||
*/
|
||||
public class GBAlarmListAdapter extends ArrayAdapter<GBAlarm> {
|
||||
public class GBAlarmListAdapter extends RecyclerView.Adapter<GBAlarmListAdapter.ViewHolder> {
|
||||
|
||||
|
||||
private final Context mContext;
|
||||
private ArrayList<GBAlarm> alarmList;
|
||||
|
||||
public GBAlarmListAdapter(Context context, ArrayList<GBAlarm> alarmList) {
|
||||
super(context, 0, alarmList);
|
||||
private List<GBAlarm> alarmList;
|
||||
|
||||
public GBAlarmListAdapter(Context context, List<GBAlarm> alarmList) {
|
||||
this.mContext = context;
|
||||
this.alarmList = alarmList;
|
||||
}
|
||||
|
||||
public GBAlarmListAdapter(Context context, Set<String> preferencesAlarmListSet) {
|
||||
super(context, 0, new ArrayList<GBAlarm>());
|
||||
|
||||
this.mContext = context;
|
||||
alarmList = new ArrayList<>();
|
||||
|
||||
@ -81,7 +78,7 @@ public class GBAlarmListAdapter extends ArrayAdapter<GBAlarm> {
|
||||
}
|
||||
|
||||
public ArrayList<? extends Alarm> getAlarmList() {
|
||||
return alarmList;
|
||||
return (ArrayList) alarmList;
|
||||
}
|
||||
|
||||
|
||||
@ -95,53 +92,26 @@ public class GBAlarmListAdapter extends ArrayAdapter<GBAlarm> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
if (alarmList != null) {
|
||||
return alarmList.size();
|
||||
}
|
||||
return 0;
|
||||
public GBAlarmListAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_alarm, parent, false);
|
||||
ViewHolder vh = new ViewHolder(view);
|
||||
return vh;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GBAlarm getItem(int position) {
|
||||
if (alarmList != null) {
|
||||
return alarmList.get(position);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public void onBindViewHolder(ViewHolder holder, final int position) {
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
if (alarmList != null) {
|
||||
return alarmList.get(position).getIndex();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
final GBAlarm alarm = alarmList.get(position);
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup parent) {
|
||||
holder.alarmDayMonday.setChecked(alarm.getRepetition(Alarm.ALARM_MON));
|
||||
holder.alarmDayTuesday.setChecked(alarm.getRepetition(Alarm.ALARM_TUE));
|
||||
holder.alarmDayWednesday.setChecked(alarm.getRepetition(Alarm.ALARM_WED));
|
||||
holder.alarmDayThursday.setChecked(alarm.getRepetition(Alarm.ALARM_THU));
|
||||
holder.alarmDayFriday.setChecked(alarm.getRepetition(Alarm.ALARM_FRI));
|
||||
holder.alarmDaySaturday.setChecked(alarm.getRepetition(Alarm.ALARM_SAT));
|
||||
holder.alarmDaySunday.setChecked(alarm.getRepetition(Alarm.ALARM_SUN));
|
||||
|
||||
final GBAlarm alarm = getItem(position);
|
||||
|
||||
if (view == null) {
|
||||
LayoutInflater inflater = (LayoutInflater) mContext
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
view = inflater.inflate(R.layout.alarm_item, parent, false);
|
||||
}
|
||||
|
||||
TextView alarmTime = (TextView) view.findViewById(R.id.alarm_item_time);
|
||||
Switch isEnabled = (Switch) view.findViewById(R.id.alarm_item_toggle);
|
||||
TextView isSmartWakeup = (TextView) view.findViewById(R.id.alarm_smart_wakeup);
|
||||
|
||||
highlightDay((TextView) view.findViewById(R.id.alarm_item_sunday), alarm.getRepetition(Alarm.ALARM_SUN));
|
||||
highlightDay((TextView) view.findViewById(R.id.alarm_item_monday), alarm.getRepetition(Alarm.ALARM_MON));
|
||||
highlightDay((TextView) view.findViewById(R.id.alarm_item_tuesday), alarm.getRepetition(Alarm.ALARM_TUE));
|
||||
highlightDay((TextView) view.findViewById(R.id.alarm_item_wednesday), alarm.getRepetition(Alarm.ALARM_WED));
|
||||
highlightDay((TextView) view.findViewById(R.id.alarm_item_thursday), alarm.getRepetition(Alarm.ALARM_THU));
|
||||
highlightDay((TextView) view.findViewById(R.id.alarm_item_friday), alarm.getRepetition(Alarm.ALARM_FRI));
|
||||
highlightDay((TextView) view.findViewById(R.id.alarm_item_saturday), alarm.getRepetition(Alarm.ALARM_SAT));
|
||||
|
||||
isEnabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
holder.isEnabled.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
alarm.setEnabled(isChecked);
|
||||
@ -149,28 +119,62 @@ public class GBAlarmListAdapter extends ArrayAdapter<GBAlarm> {
|
||||
}
|
||||
});
|
||||
|
||||
view.setOnClickListener(new View.OnClickListener() {
|
||||
holder.container.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
((ConfigureAlarms) mContext).configureAlarm(alarm);
|
||||
}
|
||||
});
|
||||
alarmTime.setText(alarm.getTime());
|
||||
isEnabled.setChecked(alarm.isEnabled());
|
||||
holder.alarmTime.setText(alarm.getTime());
|
||||
holder.isEnabled.setChecked(alarm.isEnabled());
|
||||
if (alarm.isSmartWakeup()) {
|
||||
isSmartWakeup.setVisibility(TextView.VISIBLE);
|
||||
holder.isSmartWakeup.setVisibility(TextView.VISIBLE);
|
||||
} else {
|
||||
isSmartWakeup.setVisibility(TextView.GONE);
|
||||
holder.isSmartWakeup.setVisibility(TextView.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return alarmList.size();
|
||||
}
|
||||
|
||||
private void highlightDay(TextView view, boolean isOn) {
|
||||
if (isOn) {
|
||||
view.setTextColor(Color.BLUE);
|
||||
} else {
|
||||
view.setTextColor(GBApplication.getTextColor(mContext));
|
||||
static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
CardView container;
|
||||
|
||||
TextView alarmTime;
|
||||
Switch isEnabled;
|
||||
TextView isSmartWakeup;
|
||||
|
||||
CheckedTextView alarmDayMonday;
|
||||
CheckedTextView alarmDayTuesday;
|
||||
CheckedTextView alarmDayWednesday;
|
||||
CheckedTextView alarmDayThursday;
|
||||
CheckedTextView alarmDayFriday;
|
||||
CheckedTextView alarmDaySaturday;
|
||||
CheckedTextView alarmDaySunday;
|
||||
|
||||
ViewHolder(View view) {
|
||||
super(view);
|
||||
|
||||
container = (CardView) view.findViewById(R.id.card_view);
|
||||
|
||||
alarmTime = (TextView) view.findViewById(R.id.alarm_item_time);
|
||||
isEnabled = (Switch) view.findViewById(R.id.alarm_item_toggle);
|
||||
isSmartWakeup = (TextView) view.findViewById(R.id.alarm_smart_wakeup);
|
||||
|
||||
alarmDayMonday = (CheckedTextView) view.findViewById(R.id.alarm_item_monday);
|
||||
alarmDayTuesday = (CheckedTextView) view.findViewById(R.id.alarm_item_tuesday);
|
||||
alarmDayWednesday = (CheckedTextView) view.findViewById(R.id.alarm_item_wednesday);
|
||||
alarmDayThursday = (CheckedTextView) view.findViewById(R.id.alarm_item_thursday);
|
||||
alarmDayFriday = (CheckedTextView) view.findViewById(R.id.alarm_item_friday);
|
||||
alarmDaySaturday = (CheckedTextView) view.findViewById(R.id.alarm_item_saturday);
|
||||
alarmDaySunday = (CheckedTextView) view.findViewById(R.id.alarm_item_sunday);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -384,7 +384,9 @@ public class DBHelper {
|
||||
} else {
|
||||
ensureDeviceUpToDate(device, gbDevice, session);
|
||||
}
|
||||
if (gbDevice.isInitialized()) {
|
||||
ensureDeviceAttributes(device, gbDevice, session);
|
||||
}
|
||||
|
||||
return device;
|
||||
}
|
||||
|
@ -119,4 +119,9 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBondingStyle(GBDevice device) {
|
||||
return BONDING_STYLE_ASK;
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ import android.bluetooth.le.ScanFilter;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.DrawableRes;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
@ -47,6 +46,21 @@ import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
*/
|
||||
public interface DeviceCoordinator {
|
||||
String EXTRA_DEVICE_CANDIDATE = "nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate.EXTRA_DEVICE_CANDIDATE";
|
||||
/**
|
||||
* Do not attempt to bond after discovery.
|
||||
*/
|
||||
int BONDING_STYLE_NONE = 0;
|
||||
/**
|
||||
* Bond after discovery.
|
||||
* This is not recommended, as there are mobile devices on which bonding does not work.
|
||||
* Prefer to use #BONDING_STYLE_ASK instead.
|
||||
*/
|
||||
int BONDING_STYLE_BOND = 1;
|
||||
/**
|
||||
* Let the user decide whether to bond or not after discovery.
|
||||
* Prefer this over #BONDING_STYLE_BOND
|
||||
*/
|
||||
int BONDING_STYLE_ASK = 2;
|
||||
|
||||
/**
|
||||
* Checks whether this coordinator handles the given candidate.
|
||||
@ -207,4 +221,17 @@ public interface DeviceCoordinator {
|
||||
* @return
|
||||
*/
|
||||
Class<? extends Activity> getAppsManagementActivity();
|
||||
|
||||
/**
|
||||
* Returns how/if the given device should be bonded before connecting to it.
|
||||
* @param device
|
||||
*/
|
||||
int getBondingStyle(GBDevice device);
|
||||
|
||||
/**
|
||||
* Indicates whether the device has some kind of calender we can sync to.
|
||||
* Also used for generated sunrise/sunset events
|
||||
*/
|
||||
boolean supportsCalendarEvents();
|
||||
|
||||
}
|
||||
|
@ -34,6 +34,9 @@ import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
|
||||
@ -88,6 +91,12 @@ public class DeviceManager {
|
||||
} else {
|
||||
deviceList.add(dev);
|
||||
}
|
||||
if (dev.isInitialized()) {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
DBHelper.getDevice(dev, dbHandler.getDaoSession()); // implicitly creates the device in database if not present, and updates device attributes
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
}
|
||||
updateSelectedDevice(dev);
|
||||
refreshPairedDevices();
|
||||
|
@ -176,4 +176,9 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
public Class<? extends Activity> getAppsManagementActivity() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 João Paulo Barraca
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer, João Paulo Barraca
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -121,6 +121,9 @@ public final class HPlusConstants {
|
||||
public static final byte DATA_DAY_SUMMARY_ALT = 0x39;
|
||||
public static final byte DATA_SLEEP = 0x1A;
|
||||
public static final byte DATA_VERSION = 0x18;
|
||||
public static final byte DATA_VERSION1 = 0x2E;
|
||||
|
||||
public static final byte DATA_UNKNOWN = 0x4d;
|
||||
|
||||
public static final String PREF_HPLUS_SCREENTIME = "hplus_screentime";
|
||||
public static final String PREF_HPLUS_ALLDAYHR = "hplus_alldayhr";
|
||||
@ -129,61 +132,63 @@ public final class HPlusConstants {
|
||||
public static final String PREF_HPLUS_WRIST = "hplus_wrist";
|
||||
public static final String PREF_HPLUS_SIT_START_TIME = "hplus_sit_start_time";
|
||||
public static final String PREF_HPLUS_SIT_END_TIME = "hplus_sit_end_time";
|
||||
public static final String PREF_HPLUS_UNICODE = "hplus_unicode";
|
||||
|
||||
public static final Map<Character, Byte> transliterateMap = new HashMap<Character, Byte>(){
|
||||
public static final Map<Character, byte[]> transliterateMap = new HashMap<Character, byte[]>(){
|
||||
{
|
||||
//These are missing
|
||||
put('ó', new Byte((byte) 111));
|
||||
put('Ó', new Byte((byte) 79));
|
||||
put('í', new Byte((byte) 105));
|
||||
put('Í', new Byte((byte) 73));
|
||||
put('ú', new Byte((byte) 117));
|
||||
put('Ú', new Byte((byte) 85));
|
||||
put('ó', new byte[]{(byte) 111});
|
||||
put('Ó', new byte[]{(byte) 79});
|
||||
put('í', new byte[]{(byte) 105});
|
||||
put('Í', new byte[]{(byte) 73});
|
||||
put('ú', new byte[]{(byte) 117});
|
||||
put('Ú', new byte[]{(byte) 85});
|
||||
|
||||
//These mostly belong to the extended ASCII table
|
||||
put('Ç', new Byte((byte) 128));
|
||||
put('ü', new Byte((byte) 129));
|
||||
put('é', new Byte((byte) 130));
|
||||
put('â', new Byte((byte) 131));
|
||||
put('ä', new Byte((byte) 132));
|
||||
put('à', new Byte((byte) 133));
|
||||
put('ã', new Byte((byte) 134));
|
||||
put('ç', new Byte((byte) 135));
|
||||
put('ê', new Byte((byte) 136));
|
||||
put('ë', new Byte((byte) 137));
|
||||
put('è', new Byte((byte) 138));
|
||||
put('Ï', new Byte((byte) 139));
|
||||
put('Î', new Byte((byte) 140));
|
||||
put('Ì', new Byte((byte) 141));
|
||||
put('Ã', new Byte((byte) 142));
|
||||
put('Ä', new Byte((byte) 143));
|
||||
put('É', new Byte((byte) 144));
|
||||
put('æ', new Byte((byte) 145));
|
||||
put('Æ', new Byte((byte) 146));
|
||||
put('ô', new Byte((byte) 147));
|
||||
put('ö', new Byte((byte) 148));
|
||||
put('ò', new Byte((byte) 149));
|
||||
put('û', new Byte((byte) 150));
|
||||
put('ù', new Byte((byte) 151));
|
||||
put('ÿ', new Byte((byte) 152));
|
||||
put('Ö', new Byte((byte) 153));
|
||||
put('Ü', new Byte((byte) 154));
|
||||
put('¢', new Byte((byte) 155));
|
||||
put('£', new Byte((byte) 156));
|
||||
put('¥', new Byte((byte) 157));
|
||||
put('ƒ', new Byte((byte) 159));
|
||||
put('á', new Byte((byte) 160));
|
||||
put('ñ', new Byte((byte) 164));
|
||||
put('Ñ', new Byte((byte) 165));
|
||||
put('ª', new Byte((byte) 166));
|
||||
put('º', new Byte((byte) 167));
|
||||
put('¿', new Byte((byte) 168));
|
||||
put('¬', new Byte((byte) 170));
|
||||
put('½', new Byte((byte) 171));
|
||||
put('¼', new Byte((byte) 172));
|
||||
put('¡', new Byte((byte) 173));
|
||||
put('«', new Byte((byte) 174));
|
||||
put('»', new Byte((byte) 175));
|
||||
put('Ç', new byte[]{(byte) 128});
|
||||
put('ü', new byte[]{(byte) 129});
|
||||
put('é', new byte[]{(byte) 130});
|
||||
put('â', new byte[]{(byte) 131});
|
||||
put('ä', new byte[]{(byte) 132});
|
||||
put('à', new byte[]{(byte) 133});
|
||||
put('ã', new byte[]{(byte) 134});
|
||||
put('ç', new byte[]{(byte) 135});
|
||||
put('ê', new byte[]{(byte) 136});
|
||||
put('ë', new byte[]{(byte) 137});
|
||||
put('Ï', new byte[]{(byte) 139});
|
||||
put('è', new byte[]{(byte) 138});
|
||||
put('Î', new byte[]{(byte) 140});
|
||||
put('Ì', new byte[]{(byte) 141});
|
||||
put('Ã', new byte[]{(byte) 142});
|
||||
put('Ä', new byte[]{(byte) 143});
|
||||
put('É', new byte[]{(byte) 144});
|
||||
put('æ', new byte[]{(byte) 145});
|
||||
put('Æ', new byte[]{(byte) 146});
|
||||
put('ô', new byte[]{(byte) 147});
|
||||
put('ö', new byte[]{(byte) 148});
|
||||
put('ò', new byte[]{(byte) 149});
|
||||
put('û', new byte[]{(byte) 150});
|
||||
put('ù', new byte[]{(byte) 151});
|
||||
put('ÿ', new byte[]{(byte) 152});
|
||||
put('Ö', new byte[]{(byte) 153});
|
||||
put('Ü', new byte[]{(byte) 154});
|
||||
put('¢', new byte[]{(byte) 155});
|
||||
put('£', new byte[]{(byte) 156});
|
||||
put('¥', new byte[]{(byte) 157});
|
||||
put('ƒ', new byte[]{(byte) 159});
|
||||
put('á', new byte[]{(byte) 160});
|
||||
put('ñ', new byte[]{(byte) 164});
|
||||
put('Ñ', new byte[]{(byte) 165});
|
||||
put('ª', new byte[]{(byte) 166});
|
||||
put('º', new byte[]{(byte) 167});
|
||||
put('¿', new byte[]{(byte) 168});
|
||||
put('¬', new byte[]{(byte) 170});
|
||||
put('½', new byte[]{(byte) 171});
|
||||
put('¼', new byte[]{(byte) 172});
|
||||
put('¡', new byte[]{(byte) 173});
|
||||
put('«', new byte[]{(byte) 174});
|
||||
put('»', new byte[]{(byte) 175});
|
||||
put('°', new byte[]{(byte) 0xa1, (byte) 0xe3});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer, João Paulo Barraca
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, João
|
||||
Paulo Barraca
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -24,6 +25,7 @@ import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.bluetooth.le.ScanFilter;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.ParcelUuid;
|
||||
@ -80,6 +82,16 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
|
||||
return DeviceType.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBondingStyle(GBDevice deviceCandidate){
|
||||
return BONDING_STYLE_NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceType getDeviceType() {
|
||||
return DeviceType.HPLUS;
|
||||
@ -187,7 +199,6 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
|
||||
}else{
|
||||
return HPlusConstants.ARG_TIMEMODE_12H;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static byte getUnit(String address) {
|
||||
@ -200,25 +211,25 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
|
||||
}
|
||||
}
|
||||
|
||||
public static byte getUserWeight(String address) {
|
||||
public static byte getUserWeight() {
|
||||
ActivityUser activityUser = new ActivityUser();
|
||||
|
||||
return (byte) (activityUser.getWeightKg() & 0xFF);
|
||||
}
|
||||
|
||||
public static byte getUserHeight(String address) {
|
||||
public static byte getUserHeight() {
|
||||
ActivityUser activityUser = new ActivityUser();
|
||||
|
||||
return (byte) (activityUser.getHeightCm() & 0xFF);
|
||||
}
|
||||
|
||||
public static byte getUserAge(String address) {
|
||||
public static byte getUserAge() {
|
||||
ActivityUser activityUser = new ActivityUser();
|
||||
|
||||
return (byte) (activityUser.getAge() & 0xFF);
|
||||
}
|
||||
|
||||
public static byte getUserGender(String address) {
|
||||
public static byte getUserGender() {
|
||||
ActivityUser activityUser = new ActivityUser();
|
||||
|
||||
if (activityUser.getGender() == ActivityUser.GENDER_MALE)
|
||||
@ -227,7 +238,7 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
|
||||
return HPlusConstants.ARG_GENDER_FEMALE;
|
||||
}
|
||||
|
||||
public static int getGoal(String address) {
|
||||
public static int getGoal() {
|
||||
ActivityUser activityUser = new ActivityUser();
|
||||
|
||||
return activityUser.getStepsGoal();
|
||||
@ -271,4 +282,13 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
|
||||
return prefs.getInt(HPlusConstants.PREF_HPLUS_SIT_END_TIME, 0);
|
||||
}
|
||||
|
||||
public static void setUnicodeSupport(String address, boolean state){
|
||||
SharedPreferences.Editor editor = prefs.getPreferences().edit();
|
||||
editor.putBoolean(HPlusConstants.PREF_HPLUS_UNICODE + "_" + address, state);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public static boolean getUnicodeSupport(String address){
|
||||
return (prefs.getBoolean(HPlusConstants.PREF_HPLUS_UNICODE, false));
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2017 João Paulo Barraca
|
||||
/* Copyright (C) 2017 Andreas Shimokawa, João Paulo Barraca
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
|
@ -120,6 +120,11 @@ public class LiveviewCoordinator extends AbstractDeviceCoordinator {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
// nothing to delete, yet
|
||||
|
@ -171,6 +171,11 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean hasValidUserInfo() {
|
||||
String dummyMacAddress = MiBandService.MAC_ADDRESS_FILTER_1_1A + ":00:00:00";
|
||||
try {
|
||||
|
@ -23,14 +23,24 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.alertnotification.AlertLevel;
|
||||
|
||||
public class VibrationProfile {
|
||||
public static final Context CONTEXT = GBApplication.getContext();
|
||||
public static final String ID_STACCATO = CONTEXT.getString(R.string.p_staccato);
|
||||
public static final String ID_SHORT = CONTEXT.getString(R.string.p_short);
|
||||
public static final String ID_MEDIUM = CONTEXT.getString(R.string.p_medium);
|
||||
public static final String ID_LONG = CONTEXT.getString(R.string.p_long);
|
||||
public static final String ID_WATERDROP = CONTEXT.getString(R.string.p_waterdrop);
|
||||
public static final String ID_RING = CONTEXT.getString(R.string.p_ring);
|
||||
public static final String ID_ALARM_CLOCK = CONTEXT.getString(R.string.p_alarm_clock);
|
||||
public static final String ID_STACCATO;
|
||||
public static final String ID_SHORT;
|
||||
public static final String ID_MEDIUM;
|
||||
public static final String ID_LONG;
|
||||
public static final String ID_WATERDROP;
|
||||
public static final String ID_RING;
|
||||
public static final String ID_ALARM_CLOCK;
|
||||
|
||||
static {
|
||||
Context CONTEXT = GBApplication.getContext();
|
||||
ID_STACCATO = CONTEXT.getString(R.string.p_staccato);
|
||||
ID_SHORT = CONTEXT.getString(R.string.p_short);
|
||||
ID_MEDIUM = CONTEXT.getString(R.string.p_medium);
|
||||
ID_LONG = CONTEXT.getString(R.string.p_long);
|
||||
ID_WATERDROP = CONTEXT.getString(R.string.p_waterdrop);
|
||||
ID_RING = CONTEXT.getString(R.string.p_ring);
|
||||
ID_ALARM_CLOCK = CONTEXT.getString(R.string.p_alarm_clock);
|
||||
}
|
||||
|
||||
public static VibrationProfile getProfile(String id, short repeat) {
|
||||
if (ID_STACCATO.equals(id)) {
|
||||
|
@ -20,6 +20,7 @@ package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@ -48,6 +49,7 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
|
||||
public PebbleCoordinator() {
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
|
||||
String name = candidate.getDevice().getName();
|
||||
@ -73,7 +75,7 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(GBDevice gbDevice, Device device, DaoSession session) throws GBException {
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
Long deviceId = device.getId();
|
||||
QueryBuilder<?> qb = session.getPebbleHealthActivitySampleDao().queryBuilder();
|
||||
qb.where(PebbleHealthActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities();
|
||||
@ -159,4 +161,9 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
|
||||
public Class<? extends Activity> getAppsManagementActivity() {
|
||||
return AppManagerActivity.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -120,6 +120,11 @@ public class VibratissimoCoordinator extends AbstractDeviceCoordinator {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsCalendarEvents() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
|
||||
// nothing to delete, yet
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2017 Carsten Pfeiffer
|
||||
/* Copyright (C) 2017 Carsten Pfeiffer, Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -28,7 +28,7 @@ public class AutoStartReceiver extends BroadcastReceiver {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (GBApplication.getGBPrefs().getAutoStart()) {
|
||||
if (GBApplication.getGBPrefs().getAutoStart() && Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
|
||||
Log.i(TAG, "Boot completed, starting Gadgetbridge");
|
||||
GBApplication.deviceService().start();
|
||||
}
|
||||
|
@ -0,0 +1,73 @@
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, João Paulo Barraca
|
||||
|
||||
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.externalevents;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
|
||||
/**
|
||||
* Created by jpbarraca on 13/04/2017.
|
||||
*/
|
||||
|
||||
public class BluetoothPairingRequestReceiver extends BroadcastReceiver {
|
||||
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(BluetoothConnectReceiver.class);
|
||||
|
||||
final DeviceCommunicationService service;
|
||||
|
||||
public BluetoothPairingRequestReceiver(DeviceCommunicationService service) {
|
||||
this.service = service;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
|
||||
|
||||
if (!action.equals(BluetoothDevice.ACTION_PAIRING_REQUEST)) {
|
||||
return;
|
||||
}
|
||||
|
||||
GBDevice gbDevice = service.getGBDevice();
|
||||
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
|
||||
if (gbDevice == null || device == null)
|
||||
return;
|
||||
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
|
||||
try {
|
||||
if (coordinator.getBondingStyle(gbDevice) == DeviceCoordinator.BONDING_STYLE_NONE) {
|
||||
LOG.info("Aborting unwanted pairing request");
|
||||
abortBroadcast();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.warn("Could not abort pairing request process");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -50,7 +50,7 @@ public class BluetoothStateChangeReceiver extends BroadcastReceiver {
|
||||
LOG.info("Bluetooth turned on => connecting...");
|
||||
GBApplication.deviceService().connect();
|
||||
} else if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_OFF) {
|
||||
GBApplication.quit();
|
||||
GBApplication.deviceService().disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,216 @@
|
||||
/* Copyright (C) 2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele Gobbetti,
|
||||
Daniel Hauck
|
||||
|
||||
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.externalevents;
|
||||
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Enumeration;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Hashtable;
|
||||
import java.util.List;
|
||||
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.CalendarSyncState;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.CalendarSyncStateDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class CalendarReceiver extends BroadcastReceiver {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CalendarReceiver.class);
|
||||
private Hashtable<Long, EventSyncState> eventState = new Hashtable<>();
|
||||
|
||||
private GBDevice mGBDevice;
|
||||
|
||||
private class EventSyncState {
|
||||
private int state;
|
||||
private CalendarEvents.CalendarEvent event;
|
||||
|
||||
EventSyncState(CalendarEvents.CalendarEvent event, int state) {
|
||||
this.state = state;
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
public int getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void setState(int state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public CalendarEvents.CalendarEvent getEvent() {
|
||||
return event;
|
||||
}
|
||||
|
||||
public void setEvent(CalendarEvents.CalendarEvent event) {
|
||||
this.event = event;
|
||||
}
|
||||
}
|
||||
|
||||
private static class EventState {
|
||||
private static final int NOT_SYNCED = 0;
|
||||
private static final int SYNCED = 1;
|
||||
private static final int NEEDS_UPDATE = 2;
|
||||
private static final int NEEDS_DELETE = 3;
|
||||
}
|
||||
|
||||
public CalendarReceiver(GBDevice gbDevice) {
|
||||
LOG.info("Created calendar receiver.");
|
||||
mGBDevice = gbDevice;
|
||||
onReceive(GBApplication.getContext(), new Intent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
LOG.info("got calendar changed broadcast");
|
||||
List<CalendarEvents.CalendarEvent> eventList = (new CalendarEvents()).getCalendarEventList(GBApplication.getContext());
|
||||
syncCalendar(eventList);
|
||||
}
|
||||
|
||||
public void syncCalendar(List<CalendarEvents.CalendarEvent> eventList) {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
DaoSession session = dbHandler.getDaoSession();
|
||||
syncCalendar(eventList, session);
|
||||
} catch (Exception e1) {
|
||||
GB.toast("Database Error while syncing Calendar", Toast.LENGTH_SHORT, GB.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public void syncCalendar(List<CalendarEvents.CalendarEvent> eventList, DaoSession session) {
|
||||
LOG.info("Syncing with calendar.");
|
||||
Hashtable<Long, CalendarEvents.CalendarEvent> eventTable = new Hashtable<>();
|
||||
Long deviceId = DBHelper.getDevice(mGBDevice, session).getId();
|
||||
QueryBuilder<CalendarSyncState> qb = session.getCalendarSyncStateDao().queryBuilder();
|
||||
|
||||
|
||||
for (CalendarEvents.CalendarEvent e : eventList) {
|
||||
long id = e.getId();
|
||||
eventTable.put(id, e);
|
||||
if (!eventState.containsKey(e.getId())) {
|
||||
qb = session.getCalendarSyncStateDao().queryBuilder();
|
||||
|
||||
CalendarSyncState calendarSyncState = qb.where(qb.and(CalendarSyncStateDao.Properties.DeviceId.eq(deviceId), CalendarSyncStateDao.Properties.CalendarEntryId.eq(id)))
|
||||
.build().unique();
|
||||
if (calendarSyncState == null) {
|
||||
eventState.put(id, new EventSyncState(e, EventState.NOT_SYNCED));
|
||||
LOG.info("event id=" + id + " is yet unknown to device id=" + deviceId);
|
||||
} else if (calendarSyncState.getHash() == e.hashCode()) {
|
||||
eventState.put(id, new EventSyncState(e, EventState.SYNCED));
|
||||
LOG.info("event id=" + id + " is up to date on device id=" + deviceId);
|
||||
}
|
||||
else {
|
||||
eventState.put(id, new EventSyncState(e, EventState.NEEDS_UPDATE));
|
||||
LOG.info("event id=" + id + " is not up to date on device id=" + deviceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add all missing calendar ids on the device to sync status (so that they are deleted later)
|
||||
List<CalendarSyncState> CalendarSyncStateList = qb.where(CalendarSyncStateDao.Properties.DeviceId.eq(deviceId)).build().list();
|
||||
for (CalendarSyncState CalendarSyncState : CalendarSyncStateList) {
|
||||
if (!eventState.containsKey(CalendarSyncState.getCalendarEntryId())) {
|
||||
eventState.put(CalendarSyncState.getCalendarEntryId(), new EventSyncState(null, EventState.NEEDS_DELETE));
|
||||
LOG.info("insert null event for orphanded calendar id=" + CalendarSyncState.getCalendarEntryId() + " for device=" + mGBDevice.getName());
|
||||
}
|
||||
}
|
||||
|
||||
Enumeration<Long> ids = eventState.keys();
|
||||
while (ids.hasMoreElements()) {
|
||||
qb = session.getCalendarSyncStateDao().queryBuilder();
|
||||
Long i = ids.nextElement();
|
||||
EventSyncState es = eventState.get(i);
|
||||
if (eventTable.containsKey(i)) {
|
||||
if (es.getState() == EventState.SYNCED) {
|
||||
if (!es.getEvent().equals(eventTable.get(i))) {
|
||||
eventState.put(i, new EventSyncState(eventTable.get(i), EventState.NEEDS_UPDATE));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (es.getState() == EventState.NOT_SYNCED) {
|
||||
// delete for current device only
|
||||
qb.where(qb.and(CalendarSyncStateDao.Properties.DeviceId.eq(deviceId), CalendarSyncStateDao.Properties.CalendarEntryId.eq(i)))
|
||||
.buildDelete().executeDeleteWithoutDetachingEntities();
|
||||
eventState.remove(i);
|
||||
} else {
|
||||
es.setState(EventState.NEEDS_DELETE);
|
||||
eventState.put(i, es);
|
||||
}
|
||||
}
|
||||
updateEvents(deviceId, session);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateEvents(Long deviceId, DaoSession session) {
|
||||
Enumeration<Long> ids = eventState.keys();
|
||||
while (ids.hasMoreElements()) {
|
||||
Long i = ids.nextElement();
|
||||
EventSyncState es = eventState.get(i);
|
||||
int syncState = es.getState();
|
||||
if (syncState == EventState.NOT_SYNCED || syncState == EventState.NEEDS_UPDATE) {
|
||||
CalendarEvents.CalendarEvent calendarEvent = es.getEvent();
|
||||
CalendarEventSpec calendarEventSpec = new CalendarEventSpec();
|
||||
calendarEventSpec.id = i;
|
||||
calendarEventSpec.title = calendarEvent.getTitle();
|
||||
calendarEventSpec.allDay = calendarEvent.isAllDay();
|
||||
calendarEventSpec.timestamp = calendarEvent.getBeginSeconds();
|
||||
calendarEventSpec.durationInSeconds = calendarEvent.getDurationSeconds(); //FIXME: leads to problems right now
|
||||
if (calendarEvent.isAllDay()) {
|
||||
//force the all day events to begin at midnight and last a whole day
|
||||
Calendar c = GregorianCalendar.getInstance();
|
||||
c.setTimeInMillis(calendarEvent.getBegin());
|
||||
c.set(Calendar.HOUR, 0);
|
||||
calendarEventSpec.timestamp = (int) (c.getTimeInMillis() / 1000);
|
||||
calendarEventSpec.durationInSeconds = 24 * 60 * 60;
|
||||
}
|
||||
calendarEventSpec.description = calendarEvent.getDescription();
|
||||
calendarEventSpec.location = calendarEvent.getLocation();
|
||||
calendarEventSpec.type = CalendarEventSpec.TYPE_UNKNOWN;
|
||||
if (syncState == EventState.NEEDS_UPDATE) {
|
||||
GBApplication.deviceService().onDeleteCalendarEvent(CalendarEventSpec.TYPE_UNKNOWN, i);
|
||||
}
|
||||
GBApplication.deviceService().onAddCalendarEvent(calendarEventSpec);
|
||||
es.setState(EventState.SYNCED);
|
||||
eventState.put(i, es);
|
||||
// update db
|
||||
session.insertOrReplace(new CalendarSyncState(null, deviceId, i, es.event.hashCode()));
|
||||
} else if (syncState == EventState.NEEDS_DELETE) {
|
||||
GBApplication.deviceService().onDeleteCalendarEvent(CalendarEventSpec.TYPE_UNKNOWN, i);
|
||||
eventState.remove(i);
|
||||
// delete from db for current device only
|
||||
QueryBuilder<CalendarSyncState> qb = session.getCalendarSyncStateDao().queryBuilder();
|
||||
qb.where(qb.and(CalendarSyncStateDao.Properties.DeviceId.eq(deviceId), CalendarSyncStateDao.Properties.CalendarEntryId.eq(i)))
|
||||
.buildDelete().executeDeleteWithoutDetachingEntities();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -18,7 +18,6 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.externalevents;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
@ -30,17 +29,20 @@ import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.media.MediaMetadata;
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.MediaSession;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Parcelable;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.service.notification.NotificationListenerService;
|
||||
import android.service.notification.StatusBarNotification;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.RemoteInput;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.support.v4.media.MediaMetadataCompat;
|
||||
import android.support.v4.media.session.MediaControllerCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.support.v7.app.NotificationCompat;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -76,7 +78,7 @@ public class NotificationListener extends NotificationListenerService {
|
||||
private LimitedQueue mActionLookup = new LimitedQueue(16);
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@SuppressLint("NewApi")
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
@ -89,7 +91,7 @@ public class NotificationListener extends NotificationListenerService {
|
||||
StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
|
||||
int handle = intent.getIntExtra("handle", -1);
|
||||
for (StatusBarNotification sbn : sbns) {
|
||||
if ((int) sbn.getPostTime() == handle) {
|
||||
if ((sbn.getPackageName().hashCode() * 31 + sbn.getId()) == handle) {
|
||||
if (action.equals(ACTION_OPEN)) {
|
||||
try {
|
||||
PendingIntent pi = sbn.getNotification().contentIntent;
|
||||
@ -112,7 +114,7 @@ public class NotificationListener extends NotificationListenerService {
|
||||
StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
|
||||
int handle = intent.getIntExtra("handle", -1);
|
||||
for (StatusBarNotification sbn : sbns) {
|
||||
if ((int) sbn.getPostTime() == handle) {
|
||||
if ((sbn.getPackageName().hashCode() * 31 + sbn.getId()) == handle) {
|
||||
if (GBApplication.isRunningLollipopOrLater()) {
|
||||
String key = sbn.getKey();
|
||||
NotificationListener.this.cancelNotification(key);
|
||||
@ -177,16 +179,8 @@ public class NotificationListener extends NotificationListenerService {
|
||||
|
||||
@Override
|
||||
public void onNotificationPosted(StatusBarNotification sbn) {
|
||||
/*
|
||||
* return early if DeviceCommunicationService is not running,
|
||||
* else the service would get started every time we get a notification.
|
||||
* unfortunately we cannot enable/disable NotificationListener at runtime like we do with
|
||||
* broadcast receivers because it seems to invalidate the permissions that are
|
||||
* necessary for NotificationListenerService
|
||||
*/
|
||||
if (!isServiceRunning()) {
|
||||
if (shouldIgnore(sbn))
|
||||
return;
|
||||
}
|
||||
|
||||
switch (GBApplication.getGrantedInterruptionFilter()) {
|
||||
case NotificationManager.INTERRUPTION_FILTER_ALL:
|
||||
@ -201,53 +195,8 @@ public class NotificationListener extends NotificationListenerService {
|
||||
|
||||
String source = sbn.getPackageName();
|
||||
Notification notification = sbn.getNotification();
|
||||
|
||||
if (handleMediaSessionNotification(notification))
|
||||
return;
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) {
|
||||
PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
if (powermanager.isScreenOn()) {
|
||||
// LOG.info("Not forwarding notification, screen seems to be on and settings do not allow this");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) {
|
||||
// LOG.info("Not forwarding notification, FLAG_ONGOING_EVENT is set. Notification flags: " + notification.flags);
|
||||
return;
|
||||
}
|
||||
|
||||
/* do not display messages from "android"
|
||||
* This includes keyboard selection message, usb connection messages, etc
|
||||
* Hope it does not filter out too much, we will see...
|
||||
*/
|
||||
|
||||
if (source.equals("android") ||
|
||||
source.equals("com.android.systemui") ||
|
||||
source.equals("com.android.dialer") ||
|
||||
source.equals("com.cyanogenmod.eleven")) {
|
||||
LOG.info("Not forwarding notification, is a system event");
|
||||
return;
|
||||
}
|
||||
|
||||
if (source.equals("com.moez.QKSMS") ||
|
||||
source.equals("com.android.mms") ||
|
||||
source.equals("com.sonyericsson.conversations") ||
|
||||
source.equals("com.android.messaging") ||
|
||||
source.equals("org.smssecure.smssecure")) {
|
||||
if (!"never".equals(prefs.getString("notification_mode_sms", "when_screen_off"))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (GBApplication.blacklist != null && GBApplication.blacklist.contains(source)) {
|
||||
LOG.info("Not forwarding notification, application is blacklisted");
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationSpec notificationSpec = new NotificationSpec();
|
||||
notificationSpec.id = source.hashCode() * 31 + sbn.getId();
|
||||
|
||||
// determinate Source App Name ("Label")
|
||||
PackageManager pm = getPackageManager();
|
||||
@ -261,26 +210,17 @@ public class NotificationListener extends NotificationListenerService {
|
||||
notificationSpec.sourceName = (String) pm.getApplicationLabel(ai);
|
||||
}
|
||||
|
||||
boolean preferBigText = false;
|
||||
boolean preferBigText = true; //changed to true since now we update the former ID
|
||||
|
||||
notificationSpec.type = AppNotificationType.getInstance().get(source);
|
||||
|
||||
if (source.startsWith("com.fsck.k9")) {
|
||||
// we dont want group summaries at all for k9
|
||||
if ((notification.flags & Notification.FLAG_GROUP_SUMMARY) == Notification.FLAG_GROUP_SUMMARY) {
|
||||
return;
|
||||
}
|
||||
preferBigText = true;
|
||||
}
|
||||
|
||||
if (notificationSpec.type == null) {
|
||||
notificationSpec.type = NotificationType.UNKNOWN;
|
||||
}
|
||||
|
||||
LOG.info("Processing notification from source " + source + " with flags: " + notification.flags);
|
||||
LOG.info("Processing notification " + notificationSpec.id + " from source " + source + " with flags: " + notification.flags);
|
||||
|
||||
dissectNotificationTo(notification, notificationSpec, preferBigText);
|
||||
notificationSpec.id = (int) sbn.getPostTime(); //FIMXE: a truly unique id would be better
|
||||
|
||||
// ignore Gadgetbridge's very own notifications, except for those from the debug screen
|
||||
if (getApplicationContext().getPackageName().equals(source)) {
|
||||
@ -292,11 +232,6 @@ public class NotificationListener extends NotificationListenerService {
|
||||
NotificationCompat.WearableExtender wearableExtender = new NotificationCompat.WearableExtender(notification);
|
||||
List<NotificationCompat.Action> actions = wearableExtender.getActions();
|
||||
|
||||
if (actions.isEmpty() && (notification.flags & Notification.FLAG_GROUP_SUMMARY) == Notification.FLAG_GROUP_SUMMARY) { //this could cause #395 to come back
|
||||
LOG.info("Not forwarding notification, FLAG_GROUP_SUMMARY is set and no wearable action present. Notification flags: " + notification.flags);
|
||||
return;
|
||||
}
|
||||
|
||||
for (NotificationCompat.Action act : actions) {
|
||||
if (act != null && act.getRemoteInputs() != null) {
|
||||
LOG.info("found wearable action: " + act.getTitle() + " " + sbn.getTag());
|
||||
@ -306,11 +241,17 @@ public class NotificationListener extends NotificationListenerService {
|
||||
}
|
||||
}
|
||||
|
||||
if ((notificationSpec.flags & NotificationSpec.FLAG_WEARABLE_REPLY) == 0 && NotificationCompat.isGroupSummary(notification)) { //this could cause #395 to come back
|
||||
LOG.info("Not forwarding notification, FLAG_GROUP_SUMMARY is set and no wearable action present. Notification flags: " + notification.flags);
|
||||
return;
|
||||
}
|
||||
|
||||
GBApplication.deviceService().onNotification(notificationSpec);
|
||||
}
|
||||
|
||||
private void dissectNotificationTo(Notification notification, NotificationSpec notificationSpec, boolean preferBigText) {
|
||||
Bundle extras = notification.extras;
|
||||
|
||||
Bundle extras = NotificationCompat.getExtras(notification);
|
||||
|
||||
//dumpExtras(extras);
|
||||
|
||||
@ -320,10 +261,32 @@ public class NotificationListener extends NotificationListenerService {
|
||||
}
|
||||
|
||||
CharSequence contentCS = null;
|
||||
if (extras.containsKey(Notification.EXTRA_MESSAGES)) {
|
||||
Parcelable[] parcelables = extras.getParcelableArray(NotificationCompat.EXTRA_MESSAGES);
|
||||
String contentBuilder = "";
|
||||
CharSequence sender;
|
||||
CharSequence prevSender = "";
|
||||
CharSequence message;
|
||||
for (Parcelable p : parcelables) {
|
||||
if (!(p instanceof Bundle))
|
||||
continue;
|
||||
sender = ((Bundle) p).getCharSequence("sender");
|
||||
message = ((Bundle) p).getCharSequence("text");
|
||||
if (sender == null || message == null)
|
||||
continue;
|
||||
if (!sender.equals(prevSender) && !sender.equals(notificationSpec.title)) {
|
||||
contentBuilder += sender.toString() + ": ";
|
||||
prevSender = sender;
|
||||
}
|
||||
contentBuilder += message.toString() + "\n";
|
||||
}
|
||||
contentCS = contentBuilder;
|
||||
} else {
|
||||
if (preferBigText && extras.containsKey(Notification.EXTRA_BIG_TEXT)) {
|
||||
contentCS = extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
|
||||
contentCS = extras.getCharSequence(NotificationCompat.EXTRA_BIG_TEXT);
|
||||
} else if (extras.containsKey(Notification.EXTRA_TEXT)) {
|
||||
contentCS = extras.getCharSequence(Notification.EXTRA_TEXT);
|
||||
contentCS = extras.getCharSequence(NotificationCompat.EXTRA_TEXT);
|
||||
}
|
||||
}
|
||||
if (contentCS != null) {
|
||||
notificationSpec.body = contentCS.toString();
|
||||
@ -344,31 +307,18 @@ public class NotificationListener extends NotificationListenerService {
|
||||
/**
|
||||
* Try to handle media session notifications that tell info about the current play state.
|
||||
*
|
||||
* @param notification The notification to handle.
|
||||
* @param mediaSession The mediasession to handle.
|
||||
* @return true if notification was handled, false otherwise
|
||||
*/
|
||||
public boolean handleMediaSessionNotification(Notification notification) {
|
||||
|
||||
// this code requires Android 5.0 or newer
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean handleMediaSessionNotification(MediaSessionCompat.Token mediaSession) {
|
||||
MusicSpec musicSpec = new MusicSpec();
|
||||
MusicStateSpec stateSpec = new MusicStateSpec();
|
||||
|
||||
Bundle extras = notification.extras;
|
||||
if (extras == null)
|
||||
return false;
|
||||
|
||||
if (extras.get(Notification.EXTRA_MEDIA_SESSION) == null)
|
||||
return false;
|
||||
|
||||
MediaController c;
|
||||
MediaControllerCompat c;
|
||||
try {
|
||||
c = new MediaController(getApplicationContext(), (MediaSession.Token) extras.get(Notification.EXTRA_MEDIA_SESSION));
|
||||
c = new MediaControllerCompat(getApplicationContext(), mediaSession);
|
||||
|
||||
PlaybackState s = c.getPlaybackState();
|
||||
PlaybackStateCompat s = c.getPlaybackState();
|
||||
stateSpec.position = (int) (s.getPosition() / 1000);
|
||||
stateSpec.playRate = Math.round(100 * s.getPlaybackSpeed());
|
||||
stateSpec.repeat = 1;
|
||||
@ -388,53 +338,41 @@ public class NotificationListener extends NotificationListenerService {
|
||||
break;
|
||||
}
|
||||
|
||||
MediaMetadata d = c.getMetadata();
|
||||
MediaMetadataCompat d = c.getMetadata();
|
||||
if (d == null)
|
||||
return false;
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_ARTIST))
|
||||
musicSpec.artist = d.getString(MediaMetadata.METADATA_KEY_ARTIST);
|
||||
musicSpec.artist = d.getString(MediaMetadataCompat.METADATA_KEY_ARTIST);
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_ALBUM))
|
||||
musicSpec.album = d.getString(MediaMetadata.METADATA_KEY_ALBUM);
|
||||
musicSpec.album = d.getString(MediaMetadataCompat.METADATA_KEY_ALBUM);
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_TITLE))
|
||||
musicSpec.track = d.getString(MediaMetadata.METADATA_KEY_TITLE);
|
||||
musicSpec.track = d.getString(MediaMetadataCompat.METADATA_KEY_TITLE);
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_DURATION))
|
||||
musicSpec.duration = (int) d.getLong(MediaMetadata.METADATA_KEY_DURATION) / 1000;
|
||||
musicSpec.duration = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_DURATION) / 1000;
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS))
|
||||
musicSpec.trackCount = (int) d.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS);
|
||||
musicSpec.trackCount = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_NUM_TRACKS);
|
||||
if (d.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER))
|
||||
musicSpec.trackNr = (int) d.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
|
||||
musicSpec.trackNr = (int) d.getLong(MediaMetadataCompat.METADATA_KEY_TRACK_NUMBER);
|
||||
|
||||
// finally, tell the device about it
|
||||
GBApplication.deviceService().onSetMusicInfo(musicSpec);
|
||||
GBApplication.deviceService().onSetMusicState(stateSpec);
|
||||
|
||||
return true;
|
||||
} catch (NullPointerException e) {
|
||||
} catch (NullPointerException | RemoteException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNotificationRemoved(StatusBarNotification sbn) {
|
||||
//FIXME: deduplicate code
|
||||
String source = sbn.getPackageName();
|
||||
Notification notification = sbn.getNotification();
|
||||
if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) {
|
||||
if (shouldIgnore(sbn))
|
||||
return;
|
||||
}
|
||||
|
||||
if (source.equals("android") ||
|
||||
source.equals("com.android.systemui") ||
|
||||
source.equals("com.android.dialer") ||
|
||||
source.equals("com.cyanogenmod.eleven")) {
|
||||
return;
|
||||
}
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if (prefs.getBoolean("autoremove_notifications", false)) {
|
||||
LOG.info("notification removed, will ask device to delete it");
|
||||
|
||||
GBApplication.deviceService().onDeleteNotification((int) sbn.getPostTime()); //FIMXE: a truly unique id would be better
|
||||
GBApplication.deviceService().onDeleteNotification(sbn.getPackageName().hashCode() * 31 + sbn.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@ -447,4 +385,88 @@ public class NotificationListener extends NotificationListenerService {
|
||||
LOG.debug(String.format("Notification extra: %s %s (%s)", key, value.toString(), value.getClass().getName()));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldIgnore(StatusBarNotification sbn) {
|
||||
/*
|
||||
* return early if DeviceCommunicationService is not running,
|
||||
* else the service would get started every time we get a notification.
|
||||
* unfortunately we cannot enable/disable NotificationListener at runtime like we do with
|
||||
* broadcast receivers because it seems to invalidate the permissions that are
|
||||
* necessary for NotificationListenerService
|
||||
*/
|
||||
if (!isServiceRunning() || sbn == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (shouldIgnoreSource(sbn.getPackageName()))
|
||||
return true;
|
||||
|
||||
if (shouldIgnoreNotification(sbn.getNotification()))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean shouldIgnoreSource(String source) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
|
||||
/* do not display messages from "android"
|
||||
* This includes keyboard selection message, usb connection messages, etc
|
||||
* Hope it does not filter out too much, we will see...
|
||||
*/
|
||||
|
||||
if (source.equals("android") ||
|
||||
source.equals("com.android.systemui") ||
|
||||
source.equals("com.android.dialer") ||
|
||||
source.equals("com.cyanogenmod.eleven")) {
|
||||
LOG.info("Ignoring notification, is a system event");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (source.equals("com.moez.QKSMS") ||
|
||||
source.equals("com.android.mms") ||
|
||||
source.equals("com.sonyericsson.conversations") ||
|
||||
source.equals("com.android.messaging") ||
|
||||
source.equals("org.smssecure.smssecure")) {
|
||||
if (!"never".equals(prefs.getString("notification_mode_sms", "when_screen_off"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (GBApplication.isBlacklisted(source)) {
|
||||
LOG.info("Ignoring notification, application is blacklisted");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean shouldIgnoreNotification(Notification notification) {
|
||||
|
||||
MediaSessionCompat.Token mediaSession = NotificationCompat.getMediaSession(notification);
|
||||
//try to handle media session notifications
|
||||
if (mediaSession != null && handleMediaSessionNotification(mediaSession))
|
||||
return true;
|
||||
|
||||
//ignore notifications marked as LocalOnly https://developer.android.com/reference/android/app/Notification.html#FLAG_LOCAL_ONLY
|
||||
if (NotificationCompat.getLocalOnly(notification))
|
||||
return true;
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
if (!prefs.getBoolean("notifications_generic_whenscreenon", false)) {
|
||||
PowerManager powermanager = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
if (powermanager.isScreenOn()) {
|
||||
// LOG.info("Not forwarding notification, screen seems to be on and settings do not allow this");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ((notification.flags & Notification.FLAG_ONGOING_EVENT) == Notification.FLAG_ONGOING_EVENT) {
|
||||
// LOG.info("Not forwarding notification, FLAG_ONGOING_EVENT is set. Notification flags: " + notification.flags);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -105,10 +105,10 @@ public class GBDeviceService implements DeviceService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(@Nullable GBDevice device, boolean performPair) {
|
||||
public void connect(@Nullable GBDevice device, boolean firstTime) {
|
||||
Intent intent = createIntent().setAction(ACTION_CONNECT)
|
||||
.putExtra(GBDevice.EXTRA_DEVICE, device)
|
||||
.putExtra(EXTRA_PERFORM_PAIR, performPair);
|
||||
.putExtra(EXTRA_CONNECT_FIRST_TIME, firstTime);
|
||||
invokeService(intent);
|
||||
}
|
||||
|
||||
@ -332,7 +332,8 @@ public class GBDeviceService implements DeviceService {
|
||||
.putExtra(EXTRA_CALENDAREVENT_TIMESTAMP, calendarEventSpec.timestamp)
|
||||
.putExtra(EXTRA_CALENDAREVENT_DURATION, calendarEventSpec.durationInSeconds)
|
||||
.putExtra(EXTRA_CALENDAREVENT_TITLE, calendarEventSpec.title)
|
||||
.putExtra(EXTRA_CALENDAREVENT_DESCRIPTION, calendarEventSpec.description);
|
||||
.putExtra(EXTRA_CALENDAREVENT_DESCRIPTION, calendarEventSpec.description)
|
||||
.putExtra(EXTRA_CALENDAREVENT_LOCATION, calendarEventSpec.location);
|
||||
invokeService(intent);
|
||||
}
|
||||
|
||||
|
@ -44,6 +44,9 @@ public class AppNotificationType extends HashMap<String, NotificationType> {
|
||||
put("com.sonyericsson.conversations", NotificationType.GENERIC_SMS);
|
||||
put("org.smssecure.smssecure", NotificationType.GENERIC_SMS);
|
||||
|
||||
// Generic Calendar
|
||||
put("com.android.calendar", NotificationType.GENERIC_CALENDAR);
|
||||
|
||||
// Conversations
|
||||
put("eu.siacs.conversations", NotificationType.CONVERSATIONS);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -27,4 +27,6 @@ public class CalendarEventSpec {
|
||||
public int durationInSeconds;
|
||||
public String title;
|
||||
public String description;
|
||||
public String location;
|
||||
public boolean allDay;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
Gobbetti, Daniel Hauck
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -21,15 +21,21 @@ import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.provider.CalendarContract;
|
||||
import android.provider.CalendarContract.Instances;
|
||||
import android.text.format.Time;
|
||||
import android.util.Log;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class CalendarEvents {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CalendarEvents.class);
|
||||
|
||||
// needed for pebble: time, duration, layout, reminders, actions
|
||||
// layout: type, title, subtitle, body (max 512), tinyIcon, smallIcon, largeIcon
|
||||
@ -41,13 +47,15 @@ public class CalendarEvents {
|
||||
|
||||
private static final String[] EVENT_INSTANCE_PROJECTION = new String[]{
|
||||
Instances._ID,
|
||||
|
||||
Instances.BEGIN,
|
||||
Instances.END,
|
||||
Instances.EVENT_ID,
|
||||
Instances.DURATION,
|
||||
Instances.TITLE,
|
||||
Instances.DESCRIPTION,
|
||||
Instances.EVENT_LOCATION,
|
||||
Instances.CALENDAR_DISPLAY_NAME
|
||||
Instances.CALENDAR_DISPLAY_NAME,
|
||||
Instances.ALL_DAY
|
||||
};
|
||||
|
||||
private static final int lookahead_days = 7;
|
||||
@ -62,28 +70,37 @@ public class CalendarEvents {
|
||||
private boolean fetchSystemEvents(Context mContext) {
|
||||
|
||||
Calendar cal = GregorianCalendar.getInstance();
|
||||
Long dtStart = cal.getTime().getTime();
|
||||
Long dtStart = cal.getTimeInMillis();
|
||||
cal.add(Calendar.DATE, lookahead_days);
|
||||
Long dtEnd = cal.getTime().getTime();
|
||||
Long dtEnd = cal.getTimeInMillis();
|
||||
|
||||
Uri.Builder eventsUriBuilder = CalendarContract.Instances.CONTENT_URI.buildUpon();
|
||||
Uri.Builder eventsUriBuilder = Instances.CONTENT_URI.buildUpon();
|
||||
ContentUris.appendId(eventsUriBuilder, dtStart);
|
||||
ContentUris.appendId(eventsUriBuilder, dtEnd);
|
||||
Uri eventsUri = eventsUriBuilder.build();
|
||||
|
||||
try (Cursor evtCursor = mContext.getContentResolver().query(eventsUri, EVENT_INSTANCE_PROJECTION, null, null, CalendarContract.Instances.BEGIN + " ASC")) {
|
||||
try (Cursor evtCursor = mContext.getContentResolver().query(eventsUri, EVENT_INSTANCE_PROJECTION, null, null, Instances.BEGIN + " ASC")) {
|
||||
if (evtCursor == null || evtCursor.getCount() == 0) {
|
||||
return false;
|
||||
}
|
||||
while (evtCursor.moveToNext()) {
|
||||
long start = evtCursor.getLong(1);
|
||||
long end = evtCursor.getLong(2);
|
||||
if (end == 0) {
|
||||
LOG.info("no end time, will parse duration string");
|
||||
Time time = new Time(); //FIXME: deprecated FTW
|
||||
time.parse(evtCursor.getString(3));
|
||||
end = start + time.toMillis(false);
|
||||
}
|
||||
CalendarEvent calEvent = new CalendarEvent(
|
||||
evtCursor.getLong(1),
|
||||
evtCursor.getLong(2),
|
||||
evtCursor.getLong(3),
|
||||
start,
|
||||
end,
|
||||
evtCursor.getLong(0),
|
||||
evtCursor.getString(4),
|
||||
evtCursor.getString(5),
|
||||
evtCursor.getString(6),
|
||||
evtCursor.getString(7)
|
||||
evtCursor.getString(7),
|
||||
!evtCursor.getString(8).equals("0")
|
||||
);
|
||||
calendarEventList.add(calEvent);
|
||||
}
|
||||
@ -91,7 +108,7 @@ public class CalendarEvents {
|
||||
}
|
||||
}
|
||||
|
||||
public class CalendarEvent {
|
||||
public static class CalendarEvent {
|
||||
private long begin;
|
||||
private long end;
|
||||
private long id;
|
||||
@ -99,8 +116,9 @@ public class CalendarEvents {
|
||||
private String description;
|
||||
private String location;
|
||||
private String calName;
|
||||
private boolean allDay;
|
||||
|
||||
public CalendarEvent(long begin, long end, long id, String title, String description, String location, String calName) {
|
||||
public CalendarEvent(long begin, long end, long id, String title, String description, String location, String calName, boolean allDay) {
|
||||
this.begin = begin;
|
||||
this.end = end;
|
||||
this.id = id;
|
||||
@ -108,6 +126,7 @@ public class CalendarEvents {
|
||||
this.description = description;
|
||||
this.location = location;
|
||||
this.calName = calName;
|
||||
this.allDay = allDay;
|
||||
}
|
||||
|
||||
public long getBegin() {
|
||||
@ -155,5 +174,38 @@ public class CalendarEvents {
|
||||
return calName;
|
||||
}
|
||||
|
||||
public boolean isAllDay() {
|
||||
return allDay;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object other) {
|
||||
if (other instanceof CalendarEvent) {
|
||||
CalendarEvent e = (CalendarEvent) other;
|
||||
return (this.getId() == e.getId()) &&
|
||||
Objects.equals(this.getTitle(), e.getTitle()) &&
|
||||
(this.getBegin() == e.getBegin()) &&
|
||||
Objects.equals(this.getLocation(), e.getLocation()) &&
|
||||
Objects.equals(this.getDescription(), e.getDescription()) &&
|
||||
(this.getEnd() == e.getEnd()) &&
|
||||
Objects.equals(this.getCalName(), e.getCalName()) &&
|
||||
(this.isAllDay() == e.isAllDay());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = (int) id;
|
||||
result = 31 * result + Objects.hash(title);
|
||||
result = 31 * result + Long.valueOf(begin).hashCode();
|
||||
result = 31 * result + Objects.hash(location);
|
||||
result = 31 * result + Objects.hash(description);
|
||||
result = 31 * result + Long.valueOf(end).hashCode();
|
||||
result = 31 * result + Objects.hash(calName);
|
||||
result = 31 * result + Boolean.valueOf(allDay).hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ public interface DeviceService extends EventHandler {
|
||||
String EXTRA_URI = "uri";
|
||||
String EXTRA_CONFIG = "config";
|
||||
String EXTRA_ALARMS = "alarms";
|
||||
String EXTRA_PERFORM_PAIR = "perform_pair";
|
||||
String EXTRA_CONNECT_FIRST_TIME = "connect_first_time";
|
||||
String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps";
|
||||
|
||||
String EXTRA_WEATHER_TIMESTAMP = "weather_timestamp";
|
||||
@ -129,6 +129,7 @@ public interface DeviceService extends EventHandler {
|
||||
String EXTRA_CALENDAREVENT_DURATION = "calendarevent_duration";
|
||||
String EXTRA_CALENDAREVENT_TITLE = "calendarevent_title";
|
||||
String EXTRA_CALENDAREVENT_DESCRIPTION = "calendarevent_description";
|
||||
String EXTRA_CALENDAREVENT_LOCATION = "calendarevent_location";
|
||||
|
||||
void start();
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Daniele Gobbetti
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -62,6 +63,16 @@ public class MusicSpec {
|
||||
this.duration == musicSpec.duration &&
|
||||
this.trackCount == musicSpec.trackCount &&
|
||||
this.trackNr == musicSpec.trackNr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = artist != null ? artist.hashCode() : 0;
|
||||
result = 31 * result + (album != null ? album.hashCode() : 0);
|
||||
result = 31 * result + (track != null ? track.hashCode() : 0);
|
||||
result = 31 * result + duration;
|
||||
result = 31 * result + trackCount;
|
||||
result = 31 * result + trackNr;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ public enum NotificationType {
|
||||
GENERIC_EMAIL(PebbleIconID.GENERIC_EMAIL, PebbleColor.JaegerGreen),
|
||||
GENERIC_NAVIGATION(PebbleIconID.LOCATION, PebbleColor.Orange),
|
||||
GENERIC_SMS(PebbleIconID.GENERIC_SMS, PebbleColor.VividViolet),
|
||||
GENERIC_CALENDAR(PebbleIconID.TIMELINE_CALENDAR, PebbleColor.Blue),
|
||||
FACEBOOK(PebbleIconID.NOTIFICATION_FACEBOOK, PebbleColor.Liberty),
|
||||
FACEBOOK_MESSENGER(PebbleIconID.NOTIFICATION_FACEBOOK_MESSENGER, PebbleColor.VeryLightBlue),
|
||||
RIOT(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.LavenderIndigo),
|
||||
@ -39,8 +40,8 @@ public enum NotificationType {
|
||||
GENERIC_ALARM_CLOCK(PebbleIconID.ALARM_CLOCK, PebbleColor.Red);
|
||||
// Note: if you add any more constants, update all clients as well
|
||||
|
||||
public int icon;
|
||||
public byte color;
|
||||
public final int icon;
|
||||
public final byte color;
|
||||
|
||||
NotificationType(int icon, byte color) {
|
||||
this.icon = icon;
|
||||
|
@ -88,6 +88,14 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation just calls #connect()
|
||||
*/
|
||||
@Override
|
||||
public boolean connectFirstTime() {
|
||||
return connect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConnected() {
|
||||
return gbDevice.isConnected();
|
||||
@ -310,6 +318,8 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
|
||||
context.getString(R.string.notif_battery_low_bigtext_number_of_charges, String.valueOf(deviceEvent.numCharges))
|
||||
: ""
|
||||
, context);
|
||||
} else {
|
||||
GB.removeBatteryNotification(context);
|
||||
}
|
||||
|
||||
gbDevice.sendDeviceUpdateIntent(context);
|
||||
|
@ -1,6 +1,6 @@
|
||||
/* Copyright (C) 2015-2017 Andreas Shimokawa, Avamander, Carsten Pfeiffer,
|
||||
Daniele Gobbetti, ivanovlev, Julien Pivotto, Kasha, Sergey Trofimov, Steffen
|
||||
Liebergeld, Uwe Hermann
|
||||
Daniele Gobbetti, Daniel Hauck, ivanovlev, João Paulo Barraca, Julien
|
||||
Pivotto, Kasha, Sergey Trofimov, Steffen Liebergeld, Uwe Hermann
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -18,6 +18,8 @@
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
@ -26,9 +28,11 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
@ -41,12 +45,12 @@ import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmClockReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothPairingRequestReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.CalendarReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.MusicPlaybackReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.PebbleReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.PhoneCallReceiver;
|
||||
@ -107,6 +111,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_BOO
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_DESCRIPTION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_DURATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_ID;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_LOCATION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_TIMESTAMP;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_TITLE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_TYPE;
|
||||
@ -116,6 +121,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CAL
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CANNEDMESSAGES;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CANNEDMESSAGES_TYPE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONFIG;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONNECT_FIRST_TIME;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FIND_START;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ALBUM;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ARTIST;
|
||||
@ -137,7 +143,6 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOT
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_SUBJECT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_TITLE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_NOTIFICATION_TYPE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_PERFORM_PAIR;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_URI;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_VIBRATION_INTENSITY;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WEATHER_CURRENTCONDITION;
|
||||
@ -153,6 +158,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_WEA
|
||||
|
||||
public class DeviceCommunicationService extends Service implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.class);
|
||||
@SuppressLint("StaticFieldLeak") // only used for test cases
|
||||
private static DeviceSupportFactory DEVICE_SUPPORT_FACTORY = null;
|
||||
|
||||
private boolean mStarted = false;
|
||||
@ -167,9 +173,11 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
private MusicPlaybackReceiver mMusicPlaybackReceiver = null;
|
||||
private TimeChangeReceiver mTimeChangeReceiver = null;
|
||||
private BluetoothConnectReceiver mBlueToothConnectReceiver = null;
|
||||
private BluetoothPairingRequestReceiver mBlueToothPairingRequestReceiver = null;
|
||||
private AlarmClockReceiver mAlarmClockReceiver = null;
|
||||
|
||||
private AlarmReceiver mAlarmReceiver = null;
|
||||
private CalendarReceiver mCalendarReceiver = null;
|
||||
private Random mRandom = new Random();
|
||||
|
||||
private final String[] mMusicActions = {
|
||||
@ -201,22 +209,13 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
String action = intent.getAction();
|
||||
if (action.equals(GBDevice.ACTION_DEVICE_CHANGED)) {
|
||||
GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
// FIXME: mGBDevice was null here once
|
||||
if (mGBDevice.equals(device)) {
|
||||
if (mGBDevice != null && mGBDevice.equals(device)) {
|
||||
mGBDevice = device;
|
||||
boolean enableReceivers = mDeviceSupport != null && (mDeviceSupport.useAutoConnect() || mGBDevice.isInitialized());
|
||||
setReceiversEnableState(enableReceivers);
|
||||
setReceiversEnableState(enableReceivers, mGBDevice.isInitialized(), DeviceHelper.getInstance().getCoordinator(device));
|
||||
GB.updateNotification(mGBDevice.getName() + " " + mGBDevice.getStateString(), mGBDevice.isInitialized(), context);
|
||||
|
||||
if (device.isInitialized()) {
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
DaoSession session = dbHandler.getDaoSession();
|
||||
DBHelper.getDevice(device, session); // implicitly creates the device in database if not present, and updates device attributes
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG.error("Got ACTION_DEVICE_CHANGED from unexpected device: " + mGBDevice);
|
||||
LOG.error("Got ACTION_DEVICE_CHANGED from unexpected device: " + device);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -250,7 +249,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
}
|
||||
|
||||
String action = intent.getAction();
|
||||
boolean pair = intent.getBooleanExtra(EXTRA_PERFORM_PAIR, false);
|
||||
boolean firstTime = intent.getBooleanExtra(EXTRA_CONNECT_FIRST_TIME, false);
|
||||
|
||||
if (action == null) {
|
||||
LOG.info("no action");
|
||||
@ -310,8 +309,8 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
DeviceSupport deviceSupport = mFactory.createDeviceSupport(gbDevice);
|
||||
if (deviceSupport != null) {
|
||||
setDeviceSupport(deviceSupport);
|
||||
if (pair) {
|
||||
deviceSupport.pair();
|
||||
if (firstTime) {
|
||||
deviceSupport.connectFirstTime();
|
||||
} else {
|
||||
deviceSupport.setAutoReconnect(autoReconnect);
|
||||
deviceSupport.connect();
|
||||
@ -380,6 +379,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
calendarEventSpec.durationInSeconds = intent.getIntExtra(EXTRA_CALENDAREVENT_DURATION, -1);
|
||||
calendarEventSpec.title = intent.getStringExtra(EXTRA_CALENDAREVENT_TITLE);
|
||||
calendarEventSpec.description = intent.getStringExtra(EXTRA_CALENDAREVENT_DESCRIPTION);
|
||||
calendarEventSpec.location = intent.getStringExtra(EXTRA_CALENDAREVENT_LOCATION);
|
||||
mDeviceSupport.onAddCalendarEvent(calendarEventSpec);
|
||||
break;
|
||||
}
|
||||
@ -404,7 +404,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
case ACTION_DISCONNECT: {
|
||||
mDeviceSupport.dispose();
|
||||
if (mGBDevice != null && mGBDevice.getState() == GBDevice.State.WAITING_FOR_RECONNECT) {
|
||||
setReceiversEnableState(false);
|
||||
setReceiversEnableState(false, false, null);
|
||||
mGBDevice.setState(GBDevice.State.NOT_CONNECTED);
|
||||
mGBDevice.sendDeviceUpdateIntent(this);
|
||||
}
|
||||
@ -582,9 +582,35 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
}
|
||||
|
||||
|
||||
private void setReceiversEnableState(boolean enable) {
|
||||
private void setReceiversEnableState(boolean enable, boolean initialized, DeviceCoordinator coordinator) {
|
||||
LOG.info("Setting broadcast receivers to: " + enable);
|
||||
|
||||
if (enable && initialized && coordinator != null && coordinator.supportsCalendarEvents()) {
|
||||
if (mCalendarReceiver == null && getPrefs().getBoolean("enable_calendar_sync", true)) {
|
||||
if (!(GBApplication.isRunningMarshmallowOrLater() && ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED)) {
|
||||
IntentFilter calendarIntentFilter = new IntentFilter();
|
||||
calendarIntentFilter.addAction("android.intent.action.PROVIDER_CHANGED");
|
||||
calendarIntentFilter.addDataScheme("content");
|
||||
calendarIntentFilter.addDataAuthority("com.android.calendar", null);
|
||||
mCalendarReceiver = new CalendarReceiver(mGBDevice);
|
||||
registerReceiver(mCalendarReceiver, calendarIntentFilter);
|
||||
}
|
||||
}
|
||||
if (mAlarmReceiver == null) {
|
||||
mAlarmReceiver = new AlarmReceiver();
|
||||
registerReceiver(mAlarmReceiver, new IntentFilter("DAILY_ALARM"));
|
||||
}
|
||||
} else {
|
||||
if (mCalendarReceiver != null) {
|
||||
unregisterReceiver(mCalendarReceiver);
|
||||
mCalendarReceiver = null;
|
||||
}
|
||||
if (mAlarmReceiver != null) {
|
||||
unregisterReceiver(mAlarmReceiver);
|
||||
mAlarmReceiver = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
if (mPhoneCallReceiver == null) {
|
||||
mPhoneCallReceiver = new PhoneCallReceiver();
|
||||
@ -620,9 +646,9 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
mBlueToothConnectReceiver = new BluetoothConnectReceiver(this);
|
||||
registerReceiver(mBlueToothConnectReceiver, new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED));
|
||||
}
|
||||
if (mAlarmReceiver == null) {
|
||||
mAlarmReceiver = new AlarmReceiver();
|
||||
registerReceiver(mAlarmReceiver, new IntentFilter("DAILY_ALARM"));
|
||||
if (mBlueToothPairingRequestReceiver == null) {
|
||||
mBlueToothPairingRequestReceiver = new BluetoothPairingRequestReceiver(this);
|
||||
registerReceiver(mBlueToothPairingRequestReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST));
|
||||
}
|
||||
if (mAlarmClockReceiver == null) {
|
||||
mAlarmClockReceiver = new AlarmClockReceiver();
|
||||
@ -656,9 +682,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
unregisterReceiver(mBlueToothConnectReceiver);
|
||||
mBlueToothConnectReceiver = null;
|
||||
}
|
||||
if (mAlarmReceiver != null) {
|
||||
unregisterReceiver(mAlarmReceiver);
|
||||
mAlarmReceiver = null;
|
||||
|
||||
if (mBlueToothPairingRequestReceiver != null) {
|
||||
unregisterReceiver(mBlueToothPairingRequestReceiver);
|
||||
mBlueToothPairingRequestReceiver = null;
|
||||
}
|
||||
if (mAlarmClockReceiver != null) {
|
||||
unregisterReceiver(mAlarmClockReceiver);
|
||||
@ -677,7 +704,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
super.onDestroy();
|
||||
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
setReceiversEnableState(false); // disable BroadcastReceivers
|
||||
setReceiversEnableState(false, false, null); // disable BroadcastReceivers
|
||||
|
||||
setDeviceSupport(null);
|
||||
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
@ -52,6 +52,27 @@ public interface DeviceSupport extends EventHandler {
|
||||
*/
|
||||
boolean isConnected();
|
||||
|
||||
/**
|
||||
* Attempts an initial connection to the device, typically after the user "discovered"
|
||||
* and connects to it for the first time. Some implementations may perform an additional
|
||||
* initialization or application-level pairing compared to the regular {@link #connect()}.
|
||||
* <p/>
|
||||
* Implementations may perform the connection in a synchronous or asynchronous way.
|
||||
* Returns true if a connection attempt was made. If the implementation is synchronous
|
||||
* it may also return true if the connection was successfully established, however
|
||||
* callers shall not rely on that.
|
||||
* <p/>
|
||||
* The actual connection state change (successful or not) will be reported via the
|
||||
* #getDevice device as a device change Intent.
|
||||
*
|
||||
* Note: the default implementation {@link AbstractDeviceSupport#connectFirstTime()} just
|
||||
* calls {@link #connect()}
|
||||
*
|
||||
* @see #connect()
|
||||
* @see GBDevice#ACTION_DEVICE_CHANGED
|
||||
*/
|
||||
boolean connectFirstTime();
|
||||
|
||||
/**
|
||||
* Attempts to establish a connection to the device. Implementations may perform
|
||||
* the connection in a synchronous or asynchronous way.
|
||||
@ -62,6 +83,7 @@ public interface DeviceSupport extends EventHandler {
|
||||
* The actual connection state change (successful or not) will be reported via the
|
||||
* #getDevice device as a device change Intent.
|
||||
*
|
||||
* @see #connectFirstTime()
|
||||
* @see GBDevice#ACTION_DEVICE_CHANGED
|
||||
*/
|
||||
boolean connect();
|
||||
@ -92,14 +114,6 @@ public interface DeviceSupport extends EventHandler {
|
||||
*/
|
||||
boolean getAutoReconnect();
|
||||
|
||||
/**
|
||||
* Attempts to pair and connect this device with the gadget device. Success
|
||||
* will be reported via a device change Intent.
|
||||
*
|
||||
* @see GBDevice#ACTION_DEVICE_CHANGED
|
||||
*/
|
||||
void pair();
|
||||
|
||||
/**
|
||||
* Returns the associated device this instance communicates with.
|
||||
*/
|
||||
|
@ -0,0 +1,95 @@
|
||||
/* Copyright (C) 2017 Daniele Gobbetti
|
||||
|
||||
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;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Service;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.IBinder;
|
||||
import android.os.Process;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
|
||||
|
||||
/**
|
||||
* Original source by xinghui - see https://gist.github.com/xinghui/b2ddd8cffe55c4b62f5d8846d5545bf9
|
||||
* NB: no license specified in the source code as of 2017-04-19
|
||||
*/
|
||||
public class NotificationCollectorMonitorService extends Service {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(NotificationCollectorMonitorService.class);
|
||||
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
ensureCollectorRunning();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
private void ensureCollectorRunning() {
|
||||
ComponentName collectorComponent = new ComponentName(this, NotificationListener.class);
|
||||
LOG.info("ensureCollectorRunning collectorComponent: " + collectorComponent);
|
||||
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
|
||||
boolean collectorRunning = false;
|
||||
List<ActivityManager.RunningServiceInfo> runningServices = manager.getRunningServices(Integer.MAX_VALUE);
|
||||
if (runningServices == null) {
|
||||
LOG.info("ensureCollectorRunning() runningServices is NULL");
|
||||
return;
|
||||
}
|
||||
for (ActivityManager.RunningServiceInfo service : runningServices) {
|
||||
if (service.service.equals(collectorComponent)) {
|
||||
LOG.warn("ensureCollectorRunning service - pid: " + service.pid + ", currentPID: " + Process.myPid() + ", clientPackage: " + service.clientPackage + ", clientCount: " + service.clientCount
|
||||
+ ", clientLabel: " + ((service.clientLabel == 0) ? "0" : "(" + getResources().getString(service.clientLabel) + ")"));
|
||||
if (service.pid == Process.myPid() /*&& service.clientCount > 0 && !TextUtils.isEmpty(service.clientPackage)*/) {
|
||||
collectorRunning = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (collectorRunning) {
|
||||
LOG.debug("ensureCollectorRunning: collector is running");
|
||||
return;
|
||||
}
|
||||
LOG.debug("ensureCollectorRunning: collector not running, reviving...");
|
||||
toggleNotificationListenerService();
|
||||
}
|
||||
|
||||
private void toggleNotificationListenerService() {
|
||||
LOG.debug("toggleNotificationListenerService() called");
|
||||
ComponentName thisComponent = new ComponentName(this, NotificationListener.class);
|
||||
PackageManager pm = getPackageManager();
|
||||
pm.setComponentEnabledSetting(thisComponent, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
|
||||
pm.setComponentEnabledSetting(thisComponent, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -72,6 +72,11 @@ public class ServiceDeviceSupport implements DeviceSupport {
|
||||
return delegate.isConnected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connectFirstTime() {
|
||||
return delegate.connectFirstTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean connect() {
|
||||
return delegate.connect();
|
||||
@ -112,11 +117,6 @@ public class ServiceDeviceSupport implements DeviceSupport {
|
||||
return delegate.useAutoConnect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pair() {
|
||||
delegate.pair();
|
||||
}
|
||||
|
||||
private boolean checkBusy(String notificationKind) {
|
||||
if (!flags.contains(Flags.BUSY_CHECKING)) {
|
||||
return false;
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer, JohnnySun
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer, João Paulo Barraca, JohnnySun
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -101,6 +101,7 @@ public class BleNamesResolver {
|
||||
mServices.put("00001804-0000-1000-8000-00805f9b34fb", "Tx Power");
|
||||
mServices.put("0000fee0-0000-3512-2118-0009af100700", "(Propr: Xiaomi MiLi Service)");
|
||||
mServices.put("00001530-0000-3512-2118-0009af100700", "(Propr: Xiaomi Weight Service)");
|
||||
mServices.put("14701820-620a-3973-7c78-9cfff0876abd", "(Propr: HPLUS Service)");
|
||||
|
||||
|
||||
mCharacteristics.put("00002a43-0000-1000-8000-00805f9b34fb", "Alert AlertCategory ID");
|
||||
@ -185,6 +186,8 @@ public class BleNamesResolver {
|
||||
mCharacteristics.put("00002a07-0000-1000-8000-00805f9b34fb", "Tx Power Level");
|
||||
mCharacteristics.put("00002a45-0000-1000-8000-00805f9b34fb", "Unread Alert Status");
|
||||
|
||||
mCharacteristics.put("14702856-620a-3973-7c78-9cfff0876abd", "(Propr: HPLUS Control)");
|
||||
mCharacteristics.put("14702853-620a-3973-7c78-9cfff0876abd", "(Propr: HPLUS Measurements)");
|
||||
mValueFormats.put(Integer.valueOf(52), "32bit float");
|
||||
mValueFormats.put(Integer.valueOf(50), "16bit float");
|
||||
mValueFormats.put(Integer.valueOf(34), "16bit signed int");
|
||||
|
@ -225,7 +225,7 @@ public final class BtLEQueue {
|
||||
// alive (we do not close() it). Unfortunately we sometimes have problems
|
||||
// reconnecting automatically, so we try to fix this by re-creating mBluetoothGatt.
|
||||
// Not sure if this actually works without re-initializing the device...
|
||||
if (status != 0) {
|
||||
if (mBluetoothGatt != null) {
|
||||
if (!wasInitialized || !maybeReconnect()) {
|
||||
disconnect(); // ensure that we start over cleanly next time
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ public class ValueDecoder {
|
||||
int percent = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
|
||||
if (percent > 100 || percent < 0) {
|
||||
LOG.warn("Unexpected percent value: " + percent + ": " + GattCharacteristic.toString(characteristic));
|
||||
percent = Math.max(100, Math.min(0, percent));
|
||||
percent = Math.min(100, Math.max(0, percent));
|
||||
}
|
||||
return percent;
|
||||
}
|
||||
|
@ -51,7 +51,13 @@ public class HPlusDataRecordDaySlot extends HPlusDataRecord {
|
||||
*/
|
||||
public int heartRate;
|
||||
|
||||
public HPlusDataRecordDaySlot(byte[] data) {
|
||||
private int age = 0;
|
||||
/**
|
||||
* Raw intensity calculated from calories
|
||||
*/
|
||||
public int intensity;
|
||||
|
||||
public HPlusDataRecordDaySlot(byte[] data, int age) {
|
||||
super(data, TYPE_DAY_SLOT);
|
||||
|
||||
int a = (data[4] & 0xFF) * 256 + (data[5] & 0xFF);
|
||||
@ -77,6 +83,8 @@ public class HPlusDataRecordDaySlot extends HPlusDataRecord {
|
||||
slotTime.set(Calendar.SECOND, 0);
|
||||
|
||||
timestamp = (int) (slotTime.getTimeInMillis() / 1000L);
|
||||
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
@ -101,5 +109,12 @@ public class HPlusDataRecordDaySlot extends HPlusDataRecord {
|
||||
}
|
||||
|
||||
secondsInactive += other.secondsInactive;
|
||||
|
||||
intensity = (int) ((100*heartRate)/(208-0.7*age));
|
||||
|
||||
}
|
||||
|
||||
public boolean isValid(){
|
||||
return steps != 0 || secondsInactive != 0 || heartRate != -1;
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +66,7 @@ class HPlusDataRecordRealtime extends HPlusDataRecord {
|
||||
*/
|
||||
public int intensity;
|
||||
|
||||
public HPlusDataRecordRealtime(byte[] data) {
|
||||
public HPlusDataRecordRealtime(byte[] data, int age) {
|
||||
super(data, TYPE_REALTIME);
|
||||
|
||||
if (data.length < 15) {
|
||||
@ -91,7 +91,7 @@ class HPlusDataRecordRealtime extends HPlusDataRecord {
|
||||
heartRate = ActivitySample.NOT_MEASURED;
|
||||
}
|
||||
else {
|
||||
intensity = (int) (100 * Math.max(0, Math.min((heartRate - 60) / 120.0, 1))); // TODO: Calculate a proper value
|
||||
intensity = (int) ((100*heartRate)/(208-0.7*age));
|
||||
activityKind = ActivityKind.TYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.hplus;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
import android.util.Log;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -65,6 +66,8 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
private int DAY_SUMMARY_SYNC_PERIOD = 24 * 60 * 60;
|
||||
private int DAY_SUMMARY_SYNC_RETRY_PERIOD = 30;
|
||||
|
||||
private int HELLO_PERIOD = 60 * 2;
|
||||
|
||||
private boolean mQuit = false;
|
||||
private HPlusSupport mHPlusSupport;
|
||||
|
||||
@ -76,6 +79,8 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
private Calendar mGetSleepTime = GregorianCalendar.getInstance();
|
||||
private Calendar mGetDaySummaryTime = GregorianCalendar.getInstance();
|
||||
|
||||
private Calendar mHelloTime = GregorianCalendar.getInstance();
|
||||
|
||||
private boolean mSlotsInitialSync = true;
|
||||
|
||||
private HPlusDataRecordRealtime prevRealTimeRecord = null;
|
||||
@ -88,7 +93,7 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
|
||||
public HPlusHandlerThread(GBDevice gbDevice, Context context, HPlusSupport hplusSupport) {
|
||||
super(gbDevice, context);
|
||||
|
||||
LOG.info("Initializing HPlus Handler Thread");
|
||||
mQuit = false;
|
||||
|
||||
mHPlusSupport = hplusSupport;
|
||||
@ -118,9 +123,8 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
break;
|
||||
}
|
||||
|
||||
if(!mHPlusSupport.getDevice().isConnected()){
|
||||
if(gbDevice.getState() == GBDevice.State.NOT_CONNECTED){
|
||||
quit();
|
||||
break;
|
||||
}
|
||||
|
||||
Calendar now = GregorianCalendar.getInstance();
|
||||
@ -137,25 +141,35 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
requestDaySummaryData();
|
||||
}
|
||||
|
||||
if(now.compareTo(mHelloTime) > 0){
|
||||
sendHello();
|
||||
}
|
||||
|
||||
now = GregorianCalendar.getInstance();
|
||||
waitTime = Math.min(mGetDaySummaryTime.getTimeInMillis(), Math.min(mGetDaySlotsTime.getTimeInMillis(), mGetSleepTime.getTimeInMillis())) - now.getTimeInMillis();
|
||||
waitTime = Math.min(mGetDaySummaryTime.getTimeInMillis(), Math.min(mGetDaySlotsTime.getTimeInMillis(), Math.min(mHelloTime.getTimeInMillis(), mGetSleepTime.getTimeInMillis()))) - now.getTimeInMillis();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void quit() {
|
||||
LOG.info("HPlus: Quit Handler Thread");
|
||||
mQuit = true;
|
||||
synchronized (waitObject) {
|
||||
waitObject.notify();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void sync() {
|
||||
LOG.info("HPlus: Starting data synchronization");
|
||||
|
||||
mGetSleepTime.setTimeInMillis(0);
|
||||
mGetDaySlotsTime.setTimeInMillis(0);
|
||||
mGetDaySummaryTime.setTimeInMillis(0);
|
||||
mLastSleepDayReceived.setTimeInMillis(0);
|
||||
mHelloTime = GregorianCalendar.getInstance();
|
||||
mHelloTime.add(Calendar.SECOND, HELLO_PERIOD);
|
||||
|
||||
mSlotsInitialSync = true;
|
||||
mLastSlotReceived = -1;
|
||||
@ -163,19 +177,42 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
mCurrentDaySlot = null;
|
||||
mDaySlotRecords.clear();
|
||||
|
||||
try {
|
||||
if(!mHPlusSupport.isConnected())
|
||||
mHPlusSupport.connect();
|
||||
|
||||
TransactionBuilder builder = new TransactionBuilder("startSyncDayStats");
|
||||
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_DEVICE_ID});
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_VERSION});
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_CURR_DATA});
|
||||
|
||||
builder.queue(mHPlusSupport.getQueue());
|
||||
mHPlusSupport.performConnected(builder.getTransaction());
|
||||
}catch(Exception e){
|
||||
LOG.warn("HPlus: Synchronization exception: " + e);
|
||||
}
|
||||
|
||||
synchronized (waitObject) {
|
||||
waitObject.notify();
|
||||
}
|
||||
}
|
||||
|
||||
public void sendHello(){
|
||||
try {
|
||||
TransactionBuilder builder = new TransactionBuilder("hello");
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, HPlusConstants.CMD_ACTION_HELLO);
|
||||
mHPlusSupport.performConnected(builder.getTransaction());
|
||||
|
||||
}catch(Exception e){
|
||||
|
||||
}
|
||||
mHelloTime = GregorianCalendar.getInstance();
|
||||
mHelloTime.add(Calendar.SECOND, HELLO_PERIOD);
|
||||
|
||||
synchronized (waitObject) {
|
||||
waitObject.notify();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Process a message containing information regarding a day slot
|
||||
* A slot summarizes 10 minutes of data
|
||||
@ -183,14 +220,14 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
* @param data the message from the device
|
||||
* @return boolean indicating success or fail
|
||||
*/
|
||||
public boolean processIncomingDaySlotData(byte[] data) {
|
||||
public boolean processIncomingDaySlotData(byte[] data, int age) {
|
||||
|
||||
HPlusDataRecordDaySlot record;
|
||||
|
||||
try{
|
||||
record = new HPlusDataRecordDaySlot(data);
|
||||
record = new HPlusDataRecordDaySlot(data, age);
|
||||
} catch(IllegalArgumentException e){
|
||||
LOG.debug((e.getMessage()));
|
||||
LOG.info((e.getMessage()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -254,13 +291,18 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
List<HPlusHealthActivitySample> samples = new ArrayList<>();
|
||||
|
||||
for (HPlusDataRecordDaySlot storedRecord : mDaySlotRecords) {
|
||||
|
||||
//Invalid records (no data) will be ignored
|
||||
if(!storedRecord.isValid())
|
||||
continue;
|
||||
|
||||
HPlusHealthActivitySample sample = createSample(dbHandler, storedRecord.timestamp);
|
||||
|
||||
sample.setRawHPlusHealthData(storedRecord.getRawData());
|
||||
sample.setSteps(storedRecord.steps);
|
||||
sample.setHeartRate(storedRecord.heartRate);
|
||||
sample.setRawKind(storedRecord.type);
|
||||
|
||||
sample.setRawIntensity(record.intensity);
|
||||
sample.setProvider(provider);
|
||||
samples.add(sample);
|
||||
}
|
||||
@ -269,9 +311,9 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
mDaySlotRecords.clear();
|
||||
|
||||
} catch (GBException ex) {
|
||||
LOG.debug((ex.getMessage()));
|
||||
LOG.info((ex.getMessage()));
|
||||
} catch (Exception ex) {
|
||||
LOG.debug(ex.getMessage());
|
||||
LOG.info(ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ -293,7 +335,7 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
try{
|
||||
record = new HPlusDataRecordSleep(data);
|
||||
} catch(IllegalArgumentException e){
|
||||
LOG.debug((e.getMessage()));
|
||||
LOG.info((e.getMessage()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -326,7 +368,7 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
|
||||
provider.addGBActivitySample(sample);
|
||||
} catch (Exception ex) {
|
||||
LOG.debug(ex.getMessage());
|
||||
LOG.info(ex.getMessage());
|
||||
}
|
||||
|
||||
mGetSleepTime = GregorianCalendar.getInstance();
|
||||
@ -341,13 +383,13 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
* @param data the message from the device
|
||||
* @return boolean indicating success or fail
|
||||
*/
|
||||
public boolean processRealtimeStats(byte[] data) {
|
||||
public boolean processRealtimeStats(byte[] data, int age) {
|
||||
HPlusDataRecordRealtime record;
|
||||
|
||||
try{
|
||||
record = new HPlusDataRecordRealtime(data);
|
||||
record = new HPlusDataRecordRealtime(data, age);
|
||||
} catch(IllegalArgumentException e){
|
||||
LOG.debug((e.getMessage()));
|
||||
LOG.info((e.getMessage()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -397,9 +439,9 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
|
||||
//TODO: Handle Active Time. With Overlay?
|
||||
} catch (GBException ex) {
|
||||
LOG.debug((ex.getMessage()));
|
||||
LOG.info((ex.getMessage()));
|
||||
} catch (Exception ex) {
|
||||
LOG.debug(ex.getMessage());
|
||||
LOG.info(ex.getMessage());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -417,7 +459,7 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
try{
|
||||
record = new HPlusDataRecordDaySummary(data);
|
||||
} catch(IllegalArgumentException e){
|
||||
LOG.debug((e.getMessage()));
|
||||
LOG.info((e.getMessage()));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -437,9 +479,9 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
sample.setProvider(provider);
|
||||
provider.addGBActivitySample(sample);
|
||||
} catch (GBException ex) {
|
||||
LOG.debug((ex.getMessage()));
|
||||
LOG.info((ex.getMessage()));
|
||||
} catch (Exception ex) {
|
||||
LOG.debug(ex.getMessage());
|
||||
LOG.info(ex.getMessage());
|
||||
}
|
||||
|
||||
mGetDaySummaryTime = GregorianCalendar.getInstance();
|
||||
@ -454,11 +496,23 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
* @return boolean indicating success or fail
|
||||
*/
|
||||
public boolean processVersion(byte[] data) {
|
||||
int major = data[2] & 0xFF;
|
||||
int minor = data[1] & 0xFF;
|
||||
int major, minor;
|
||||
|
||||
if(data.length >= 11){
|
||||
major = data[10] & 0xFF;
|
||||
minor = data[9] & 0xFF;
|
||||
|
||||
int hwMajor = data[2] & 0xFF;
|
||||
int hwMinor = data[1] & 0xFF;
|
||||
|
||||
getDevice().setFirmwareVersion2(hwMajor + "." + hwMinor);
|
||||
mHPlusSupport.setUnicodeSupport((data[3] != 0));
|
||||
}else {
|
||||
major = data[2] & 0xFF;
|
||||
minor = data[1] & 0xFF;
|
||||
}
|
||||
|
||||
getDevice().setFirmwareVersion(major + "." + minor);
|
||||
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
|
||||
return true;
|
||||
@ -468,10 +522,13 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
* Issue a message requesting the next batch of sleep data
|
||||
*/
|
||||
private void requestNextSleepData() {
|
||||
try {
|
||||
TransactionBuilder builder = new TransactionBuilder("requestSleepStats");
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_SLEEP});
|
||||
builder.queue(mHPlusSupport.getQueue());
|
||||
mHPlusSupport.performConnected(builder.getTransaction());
|
||||
}catch(Exception e){
|
||||
|
||||
}
|
||||
|
||||
mGetSleepTime = GregorianCalendar.getInstance();
|
||||
mGetSleepTime.add(GregorianCalendar.SECOND, SLEEP_SYNC_RETRY_PERIOD);
|
||||
@ -519,19 +576,26 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
mLastSlotRequested = nextHour * 6 + (nextMinute / 10);
|
||||
|
||||
byte[] msg = new byte[]{HPlusConstants.CMD_GET_ACTIVE_DAY, hour, minute, nextHour, nextMinute};
|
||||
try {
|
||||
|
||||
TransactionBuilder builder = new TransactionBuilder("getNextDaySlot");
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, msg);
|
||||
builder.queue(mHPlusSupport.getQueue());
|
||||
mHPlusSupport.performConnected(builder.getTransaction());
|
||||
}catch(Exception e){
|
||||
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Request a batch of data with the summary of the previous days
|
||||
*/
|
||||
public void requestDaySummaryData(){
|
||||
try {
|
||||
TransactionBuilder builder = new TransactionBuilder("startSyncDaySummary");
|
||||
builder.write(mHPlusSupport.ctrlCharacteristic, new byte[]{HPlusConstants.CMD_GET_DAY_DATA});
|
||||
builder.queue(mHPlusSupport.getQueue());
|
||||
mHPlusSupport.performConnected(builder.getTransaction());
|
||||
}catch(Exception e){
|
||||
|
||||
}
|
||||
mGetDaySummaryTime = GregorianCalendar.getInstance();
|
||||
mGetDaySummaryTime.add(Calendar.SECOND, DAY_SUMMARY_SYNC_RETRY_PERIOD);
|
||||
}
|
||||
@ -560,4 +624,8 @@ class HPlusHandlerThread extends GBDeviceIoThread {
|
||||
return sample;
|
||||
}
|
||||
|
||||
public void setHPlusSupport(HPlusSupport HPlusSupport) {
|
||||
LOG.info("Updating HPlusSupport object");
|
||||
this.mHPlusSupport = HPlusSupport;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* Copyright (C) 2016-2017 Alberto, Andreas Shimokawa, ivanovlev, João
|
||||
Paulo Barraca
|
||||
/* Copyright (C) 2016-2017 Alberto, Andreas Shimokawa, Carsten Pfeiffer,
|
||||
ivanovlev, João Paulo Barraca
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -38,12 +38,14 @@ import org.slf4j.LoggerFactory;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
@ -52,14 +54,13 @@ import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfoProfile;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
@ -72,6 +73,8 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
public BluetoothGattCharacteristic ctrlCharacteristic = null;
|
||||
public BluetoothGattCharacteristic measureCharacteristic = null;
|
||||
|
||||
private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
|
||||
|
||||
private HPlusHandlerThread syncHelper;
|
||||
private DeviceType deviceType = DeviceType.UNKNOWN;
|
||||
|
||||
@ -87,11 +90,9 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
public HPlusSupport(DeviceType type) {
|
||||
super(LOG);
|
||||
|
||||
LOG.info("HPlusSupport Instance Created");
|
||||
deviceType = type;
|
||||
|
||||
addSupportedService(GattService.UUID_SERVICE_GENERIC_ACCESS);
|
||||
addSupportedService(GattService.UUID_SERVICE_GENERIC_ATTRIBUTE);
|
||||
addSupportedService(HPlusConstants.UUID_SERVICE_HP);
|
||||
|
||||
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
|
||||
@ -102,7 +103,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
LOG.debug("Dispose");
|
||||
LOG.info("Dispose");
|
||||
LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext());
|
||||
broadcastManager.unregisterReceiver(mReceiver);
|
||||
|
||||
@ -113,31 +114,36 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
|
||||
LOG.debug("Initializing");
|
||||
LOG.info("Initializing");
|
||||
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext()));
|
||||
gbDevice.setState(GBDevice.State.INITIALIZING);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
|
||||
measureCharacteristic = getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_MEASURE);
|
||||
ctrlCharacteristic = getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_CONTROL);
|
||||
|
||||
getDevice().setFirmwareVersion("N/A");
|
||||
getDevice().setFirmwareVersion2("N/A");
|
||||
|
||||
syncHelper = new HPlusHandlerThread(getDevice(), getContext(), this);
|
||||
|
||||
//Initialize device
|
||||
sendUserInfo(builder); //Sync preferences
|
||||
|
||||
|
||||
requestDeviceInfo(builder);
|
||||
|
||||
setInitialized(builder);
|
||||
|
||||
syncHelper.start();
|
||||
|
||||
builder.notify(getCharacteristic(HPlusConstants.UUID_CHARACTERISTIC_MEASURE), true);
|
||||
builder.setGattCallback(this);
|
||||
builder.notify(measureCharacteristic, true);
|
||||
//Initialize device
|
||||
sendUserInfo(builder); //Sync preferences
|
||||
|
||||
gbDevice.setState(GBDevice.State.INITIALIZED);
|
||||
gbDevice.sendDeviceUpdateIntent(getContext());
|
||||
|
||||
if(syncHelper == null) {
|
||||
syncHelper = new HPlusHandlerThread(getDevice(), getContext(), this);
|
||||
syncHelper.start();
|
||||
}
|
||||
syncHelper.sync();
|
||||
|
||||
getDevice().setFirmwareVersion("N/A");
|
||||
getDevice().setFirmwareVersion2("N/A");
|
||||
|
||||
requestDeviceInfo(builder);
|
||||
|
||||
LOG.info("Initialization Done");
|
||||
|
||||
return builder;
|
||||
}
|
||||
@ -286,7 +292,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
private HPlusSupport setWeight(TransactionBuilder transaction) {
|
||||
byte value = HPlusCoordinator.getUserWeight(getDevice().getAddress());
|
||||
byte value = HPlusCoordinator.getUserWeight();
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.CMD_SET_WEIGHT,
|
||||
value
|
||||
@ -296,7 +302,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
private HPlusSupport setHeight(TransactionBuilder transaction) {
|
||||
byte value = HPlusCoordinator.getUserHeight(getDevice().getAddress());
|
||||
byte value = HPlusCoordinator.getUserHeight();
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.CMD_HEIGHT,
|
||||
value
|
||||
@ -307,7 +313,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
|
||||
private HPlusSupport setAge(TransactionBuilder transaction) {
|
||||
byte value = HPlusCoordinator.getUserAge(getDevice().getAddress());
|
||||
byte value = HPlusCoordinator.getUserAge();
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.CMD_SET_AGE,
|
||||
value
|
||||
@ -317,7 +323,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
private HPlusSupport setGender(TransactionBuilder transaction) {
|
||||
byte value = HPlusCoordinator.getUserGender(getDevice().getAddress());
|
||||
byte value = HPlusCoordinator.getUserGender();
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.CMD_SET_GENDER,
|
||||
value
|
||||
@ -328,7 +334,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
|
||||
private HPlusSupport setGoal(TransactionBuilder transaction) {
|
||||
int value = HPlusCoordinator.getGoal(getDevice().getAddress());
|
||||
int value = HPlusCoordinator.getGoal();
|
||||
transaction.write(ctrlCharacteristic, new byte[]{
|
||||
HPlusConstants.CMD_SET_GOAL,
|
||||
(byte) ((value / 256) & 0xff),
|
||||
@ -403,22 +409,11 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
return this;
|
||||
}
|
||||
|
||||
private void setInitialized(TransactionBuilder builder) {
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean useAutoConnect() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pair() {
|
||||
|
||||
LOG.debug("Pair");
|
||||
}
|
||||
|
||||
private void handleDeviceInfo(DeviceInfo info) {
|
||||
LOG.warn("Device info: " + info);
|
||||
}
|
||||
@ -426,7 +421,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
@Override
|
||||
public void onNotification(NotificationSpec notificationSpec) {
|
||||
//TODO: Show different notifications according to source as Band supports this
|
||||
//LOG.debug("OnNotification: Title: "+notificationSpec.title+" Body: "+notificationSpec.body+" Source: "+notificationSpec.sourceName+" Sender: "+notificationSpec.sender+" Subject: "+notificationSpec.subject);
|
||||
//LOG.info("OnNotification: Title: "+notificationSpec.title+" Body: "+notificationSpec.body+" Source: "+notificationSpec.sourceName+" Sender: "+notificationSpec.sender+" Subject: "+notificationSpec.subject);
|
||||
showText(notificationSpec.title, notificationSpec.body);
|
||||
}
|
||||
|
||||
@ -437,18 +432,24 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onSetTime() {
|
||||
TransactionBuilder builder = new TransactionBuilder("time");
|
||||
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("time");
|
||||
|
||||
setCurrentDate(builder);
|
||||
setCurrentTime(builder);
|
||||
performConnected(builder.getTransaction());
|
||||
}catch(IOException e){
|
||||
|
||||
builder.queue(getQueue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
|
||||
|
||||
TransactionBuilder builder = new TransactionBuilder("alarm");
|
||||
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("alarm");
|
||||
|
||||
for (Alarm alarm : alarms) {
|
||||
|
||||
@ -468,9 +469,11 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
setAlarm(builder, null);
|
||||
builder.queue(getQueue());
|
||||
performConnected(builder.getTransaction());
|
||||
|
||||
GB.toast(getContext(), getContext().getString(R.string.user_feedback_all_alarms_disabled), Toast.LENGTH_SHORT, GB.INFO);
|
||||
}catch(Exception e){}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -487,12 +490,11 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
|
||||
LOG.debug("Canned Messages: " + cannedMessagesSpec);
|
||||
LOG.info("Canned Messages: " + cannedMessagesSpec);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetMusicState(MusicStateSpec stateSpec) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -537,35 +539,45 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onFetchActivityData() {
|
||||
if (syncHelper != null)
|
||||
|
||||
if (syncHelper == null){
|
||||
syncHelper = new HPlusHandlerThread(gbDevice, getContext(), this);
|
||||
syncHelper.start();
|
||||
}
|
||||
|
||||
syncHelper.sync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReboot() {
|
||||
try {
|
||||
getQueue().clear();
|
||||
|
||||
TransactionBuilder builder = new TransactionBuilder("Shutdown");
|
||||
TransactionBuilder builder = performInitialized("Shutdown");
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SHUTDOWN, HPlusConstants.ARG_SHUTDOWN_EN});
|
||||
builder.queue(getQueue());
|
||||
performConnected(builder.getTransaction());
|
||||
}catch(Exception e){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHeartRateTest() {
|
||||
getQueue().clear();
|
||||
|
||||
TransactionBuilder builder = new TransactionBuilder("HeartRateTest");
|
||||
try{
|
||||
TransactionBuilder builder = performInitialized("HeartRateTest");
|
||||
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_HEARTRATE_STATE, HPlusConstants.ARG_HEARTRATE_MEASURE_ON}); //Set Real Time... ?
|
||||
builder.queue(getQueue());
|
||||
performConnected(builder.getTransaction());
|
||||
}catch(Exception e){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
|
||||
getQueue().clear();
|
||||
|
||||
TransactionBuilder builder = new TransactionBuilder("realTimeHeartMeasurement");
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("realTimeHeartMeasurement");
|
||||
byte state;
|
||||
|
||||
if (enable)
|
||||
@ -574,9 +586,11 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
state = HPlusConstants.ARG_HEARTRATE_ALLDAY_OFF;
|
||||
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_ALLDAY_HRM, state});
|
||||
builder.queue(getQueue());
|
||||
performConnected(builder.getTransaction());
|
||||
}catch(Exception e){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFindDevice(boolean start) {
|
||||
@ -584,7 +598,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
TransactionBuilder builder = performInitialized("findMe");
|
||||
|
||||
setFindMe(builder, start);
|
||||
builder.queue(getQueue());
|
||||
performConnected(builder.getTransaction());
|
||||
} catch (IOException e) {
|
||||
GB.toast(getContext(), "Error toggling Find Me: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
}
|
||||
@ -593,8 +607,6 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onSetConstantVibration(int intensity) {
|
||||
getQueue().clear();
|
||||
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("vibration");
|
||||
|
||||
@ -605,7 +617,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
msg[i + 1] = (byte) "GadgetBridge".charAt(i);
|
||||
|
||||
builder.write(ctrlCharacteristic, msg);
|
||||
builder.queue(getQueue());
|
||||
performConnected(builder.getTransaction());
|
||||
} catch (IOException e) {
|
||||
GB.toast(getContext(), "Error setting Vibration: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
}
|
||||
@ -634,13 +646,13 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
@Override
|
||||
public void onSendConfiguration(String config) {
|
||||
LOG.debug("Send Configuration: " + config);
|
||||
LOG.info("Send Configuration: " + config);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTestNewFunction() {
|
||||
LOG.debug("Test New Function");
|
||||
LOG.info("Test New Function");
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -648,17 +660,13 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
}
|
||||
|
||||
public void setUnicodeSupport(boolean support){
|
||||
HPlusCoordinator.setUnicodeSupport(gbDevice.getAddress(), support);
|
||||
}
|
||||
|
||||
|
||||
private void showIncomingCall(String name, String rawNumber) {
|
||||
try {
|
||||
StringBuilder number = new StringBuilder();
|
||||
|
||||
//Clean up number as the device only accepts digits
|
||||
for(char c : rawNumber.toCharArray()){
|
||||
if(Character.isDigit(c)){
|
||||
number.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
TransactionBuilder builder = performInitialized("incomingCall");
|
||||
|
||||
@ -668,11 +676,10 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
//Show Call Icon
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_INCOMING_CALL, HPlusConstants.ARG_INCOMING_CALL});
|
||||
|
||||
if(name != null) {
|
||||
byte[] msg = new byte[13];
|
||||
|
||||
|
||||
//Show call name
|
||||
|
||||
for (int i = 0; i < msg.length; i++)
|
||||
msg[i] = ' ';
|
||||
|
||||
@ -685,9 +692,19 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
msg[0] = HPlusConstants.CMD_ACTION_DISPLAY_TEXT_NAME_CN;
|
||||
builder.write(ctrlCharacteristic, msg);
|
||||
}
|
||||
|
||||
builder.wait(200);
|
||||
msg = msg.clone();
|
||||
if(rawNumber != null) {
|
||||
StringBuilder number = new StringBuilder();
|
||||
|
||||
//Clean up number as the device only accepts digits
|
||||
for (char c : rawNumber.toCharArray()) {
|
||||
if (Character.isDigit(c)) {
|
||||
number.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
byte[] msg = new byte[13];
|
||||
|
||||
//Show call number
|
||||
for (int i = 0; i < msg.length; i++)
|
||||
@ -698,9 +715,11 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
msg[0] = HPlusConstants.CMD_SET_INCOMING_CALL_NUMBER;
|
||||
|
||||
builder.wait(200);
|
||||
builder.write(ctrlCharacteristic, msg);
|
||||
}
|
||||
|
||||
builder.queue(getQueue());
|
||||
performConnected(builder.getTransaction());
|
||||
} catch (IOException e) {
|
||||
GB.toast(getContext(), "Error showing incoming call: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
|
||||
@ -708,7 +727,6 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
private void showText(String title, String body) {
|
||||
LOG.debug("Show Notification: "+title+" --> "+body);
|
||||
try {
|
||||
TransactionBuilder builder = performInitialized("notification");
|
||||
|
||||
@ -726,6 +744,8 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
int length = messageBytes.length / 17;
|
||||
|
||||
length = length > 5 ? 5 : length;
|
||||
|
||||
builder.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_INCOMING_MESSAGE, HPlusConstants.ARG_INCOMING_MESSAGE});
|
||||
|
||||
int remaining = Math.min(255, (messageBytes.length % 17 > 0) ? length + 1 : length);
|
||||
@ -762,7 +782,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
msg[2] = (byte) remaining;
|
||||
|
||||
builder.write(ctrlCharacteristic, msg);
|
||||
builder.queue(getQueue());
|
||||
performConnected(builder.getTransaction());
|
||||
} catch (IOException e) {
|
||||
GB.toast(getContext(), "Error showing device Notification: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
|
||||
@ -772,6 +792,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
private void close() {
|
||||
if (syncHelper != null) {
|
||||
syncHelper.quit();
|
||||
syncHelper.interrupt();
|
||||
syncHelper = null;
|
||||
}
|
||||
}
|
||||
@ -793,9 +814,12 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
byte[] cs;
|
||||
|
||||
if (HPlusConstants.transliterateMap.containsKey(c)) {
|
||||
cs = new byte[] {HPlusConstants.transliterateMap.get(c)};
|
||||
cs = HPlusConstants.transliterateMap.get(c);
|
||||
} else {
|
||||
try {
|
||||
if(HPlusCoordinator.getUnicodeSupport(this.gbDevice.getAddress()))
|
||||
cs = c.toString().getBytes("Unicode");
|
||||
else
|
||||
cs = c.toString().getBytes("GB2312");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
//Fallback. Result string may be strange, but better than nothing
|
||||
@ -823,10 +847,15 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
switch (data[0]) {
|
||||
case HPlusConstants.DATA_VERSION:
|
||||
case HPlusConstants.DATA_VERSION1:
|
||||
return syncHelper.processVersion(data);
|
||||
|
||||
case HPlusConstants.DATA_STATS:
|
||||
return syncHelper.processRealtimeStats(data);
|
||||
boolean result = syncHelper.processRealtimeStats(data, HPlusCoordinator.getUserAge());
|
||||
if (result) {
|
||||
processExtraInfo (data);
|
||||
}
|
||||
return result;
|
||||
|
||||
case HPlusConstants.DATA_SLEEP:
|
||||
return syncHelper.processIncomingSleepData(data);
|
||||
@ -836,12 +865,54 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
|
||||
|
||||
case HPlusConstants.DATA_DAY_SUMMARY:
|
||||
case HPlusConstants.DATA_DAY_SUMMARY_ALT:
|
||||
return syncHelper.processIncomingDaySlotData(data);
|
||||
|
||||
return syncHelper.processIncomingDaySlotData(data, HPlusCoordinator.getUserAge());
|
||||
case HPlusConstants.DATA_UNKNOWN:
|
||||
return true;
|
||||
default:
|
||||
LOG.debug("Unhandled characteristic changed: " + characteristicUUID);
|
||||
|
||||
LOG.info("Unhandled characteristic change: " + characteristicUUID + " code: " + Arrays.toString(data));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void processExtraInfo (byte[] data) {
|
||||
try {
|
||||
HPlusDataRecordRealtime record = new HPlusDataRecordRealtime(data, HPlusCoordinator.getUserAge());
|
||||
|
||||
handleBatteryInfo(record.battery);
|
||||
|
||||
String DEVINFO_STEP = getContext().getString(R.string.chart_steps) + ": ";
|
||||
String DEVINFO_DISTANCE = getContext().getString(R.string.distance) + ": ";
|
||||
String DEVINFO_CALORY = getContext().getString(R.string.calories) + ": ";
|
||||
String DEVINFO_HEART = getContext().getString(R.string.charts_legend_heartrate);
|
||||
|
||||
String info = "";
|
||||
if (record.steps > 0) {
|
||||
info += DEVINFO_STEP + String.valueOf(record.steps) + " ";
|
||||
}
|
||||
if (record.distance > 0) {
|
||||
info += DEVINFO_DISTANCE + String.valueOf(record.distance) + " ";
|
||||
}
|
||||
if (record.calories > 0) {
|
||||
info += DEVINFO_CALORY + String.valueOf(record.calories) + " ";
|
||||
}
|
||||
if (record.heartRate > 0) {
|
||||
info += DEVINFO_HEART + String.valueOf(record.heartRate) + " ";
|
||||
}
|
||||
|
||||
if (!info.equals("")) {
|
||||
getDevice().addDeviceInfo(new GenericItem("", info));
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.info((e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private void handleBatteryInfo(byte data) {
|
||||
if (batteryCmd.level != (short) data) {
|
||||
batteryCmd.level = (short) data;
|
||||
handleGBDeviceEvent(batteryCmd);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Daniele Gobbetti
|
||||
/* Copyright (C) 2016-2017 Carsten Pfeiffer, Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -197,8 +197,6 @@ public class LiveviewIoThread extends GBDeviceIoThread {
|
||||
break;
|
||||
case HEADER_LEN:
|
||||
int headerSize = 0xff & incoming[0];
|
||||
if (headerSize < 0)
|
||||
throw new IOException();
|
||||
state = ReaderState.HEADER;
|
||||
incoming = new byte[headerSize];
|
||||
break;
|
||||
|
@ -217,12 +217,13 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pair() {
|
||||
public boolean connectFirstTime() {
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (connect()) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public DeviceInfo getDeviceInfo() {
|
||||
|
@ -267,13 +267,9 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pair() {
|
||||
public boolean connectFirstTime() {
|
||||
needsAuth = true;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
if (connect()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return super.connect();
|
||||
}
|
||||
|
||||
private MiBand2Support sendDefaultNotification(TransactionBuilder builder, SimpleNotification simpleNotification, short repeat, BtLEAction extraAction) {
|
||||
@ -810,7 +806,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
|
||||
}
|
||||
|
||||
private void handleButtonPressed(byte[] value) {
|
||||
LOG.info("Button pressed: " + value);
|
||||
LOG.info("Button pressed");
|
||||
logMessageContent(value);
|
||||
}
|
||||
|
||||
|
@ -216,7 +216,7 @@ public class FetchActivityOperation extends AbstractMiBand2Operation {
|
||||
int len = value.length;
|
||||
|
||||
if (len % 4 != 1) {
|
||||
throw new AssertionError("Unexpected activity array size: " + value);
|
||||
throw new AssertionError("Unexpected activity array size: " + len);
|
||||
}
|
||||
|
||||
for (int i = 1; i < len; i+=4) {
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Daniele Gobbetti
|
||||
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
|
||||
Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -69,8 +70,8 @@ class AppMessageHandlerZalewszczak extends AppMessageHandler {
|
||||
}
|
||||
|
||||
ArrayList<Pair<Integer, Object>> pairs = new ArrayList<>(2);
|
||||
pairs.add(new Pair<>(KEY_TEMP, (Object) (Math.round(weatherSpec.currentTemp - 273) + "C")));
|
||||
pairs.add(new Pair<>(KEY_ICON, (Object) (getIconForConditionCode(weatherSpec.currentConditionCode))));
|
||||
pairs.add(new Pair<Integer, Object>(KEY_TEMP, weatherSpec.currentTemp - 273 + "C"));
|
||||
pairs.add(new Pair<Integer, Object>(KEY_ICON, getIconForConditionCode(weatherSpec.currentConditionCode)));
|
||||
byte[] weatherMessage = mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs);
|
||||
|
||||
ByteBuffer buf = ByteBuffer.allocate(weatherMessage.length);
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2016-2017 Daniele Gobbetti
|
||||
/* Copyright (C) 2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele Gobbetti
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
@ -31,15 +31,9 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
class DatalogSessionAnalytics extends DatalogSession {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionAnalytics.class);
|
||||
private GBDeviceEventBatteryInfo mGBDeviceEventBatteryInfo = new GBDeviceEventBatteryInfo();
|
||||
private GBDevice mGBDevice;
|
||||
|
||||
DatalogSessionAnalytics(byte id, UUID uuid, int timestamp, int tag, byte itemType, short itemSize, GBDevice device) {
|
||||
super(id, uuid, timestamp, tag, itemType, itemSize);
|
||||
if (mGBDevice == null || !device.equals(mGBDevice)) { //prevent showing information of other pebble watches when switching devices
|
||||
mGBDevice = device;
|
||||
mGBDeviceEventBatteryInfo.state = BatteryState.UNKNOWN;
|
||||
}
|
||||
|
||||
// The default notification should not be too bad (one per hour) but we can override this if needed
|
||||
//mGBDevice.setBatteryThresholdPercent((short) 5);
|
||||
|
||||
@ -56,11 +50,13 @@ class DatalogSessionAnalytics extends DatalogSession {
|
||||
datalogMessage.position(datalogMessage.position() + 12);
|
||||
short reportedMilliVolts = datalogMessage.getShort();
|
||||
|
||||
LOG.info("Battery reading for TS " + messageTS + " is: " + reportedMilliVolts + " milliVolts, mapped to percentage: " + milliVoltstoPercentage(reportedMilliVolts));
|
||||
datalogMessage.position(datalogMessage.position() + 2);
|
||||
byte reportedPercentage = datalogMessage.get();
|
||||
|
||||
LOG.info("Battery reading for TS " + messageTS + " is: " + reportedMilliVolts + " milliVolts, percentage: " + reportedPercentage);
|
||||
if (messageTS > 0 && reportedMilliVolts < 5000) { //some safety checks
|
||||
mGBDeviceEventBatteryInfo.state = BatteryState.BATTERY_NORMAL;
|
||||
mGBDeviceEventBatteryInfo.level = milliVoltstoPercentage(reportedMilliVolts);
|
||||
mGBDeviceEventBatteryInfo.level = reportedPercentage;
|
||||
|
||||
return new GBDeviceEvent[]{mGBDeviceEventBatteryInfo, null};
|
||||
} else { //invalid data, but we ack nevertheless
|
||||
@ -68,30 +64,4 @@ class DatalogSessionAnalytics extends DatalogSession {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private short milliVoltstoPercentage(short batteryMilliVolts) {
|
||||
if (batteryMilliVolts > 4145) { //(4146 is still 100, next reported value is already 90)
|
||||
return 100;
|
||||
} else if (batteryMilliVolts > 4053) { //(4054 is still 90, next reported value is already 80)
|
||||
return 90;
|
||||
} else if (batteryMilliVolts > 4000) { //guessed
|
||||
return 80;
|
||||
} else if (batteryMilliVolts > 3880) { //confirmed
|
||||
return 70;
|
||||
} else if (batteryMilliVolts > 3855) { //probably
|
||||
return 60;
|
||||
} else if (batteryMilliVolts > 3780) { //3781 is still 50, next reading is 3776 but percentage on pebble unknown
|
||||
return 50;
|
||||
} else if (batteryMilliVolts >= 3750) { //3750 is still 40, next reported value is 3746 and already 30
|
||||
return 40;
|
||||
} else if (batteryMilliVolts > 3720) { //3723 is still 30, next reported value is 3719 and already 20
|
||||
return 30;
|
||||
} else if (batteryMilliVolts > 3680) { //3683 is still 20, next reported value is 3675 and already 10
|
||||
return 20;
|
||||
} else if (batteryMilliVolts > 3650) { //3657 is still 10
|
||||
return 10;
|
||||
} else {
|
||||
return 0; //or -1 for invalid?
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.SimpleTimeZone;
|
||||
@ -514,6 +515,8 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
public byte[] encodeAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
|
||||
long id = calendarEventSpec.id != -1 ? calendarEventSpec.id : mRandom.nextLong();
|
||||
int iconId;
|
||||
ArrayList<Pair<Integer, Object>> attributes = new ArrayList<>();
|
||||
attributes.add(new Pair<>(1, (Object) calendarEventSpec.title));
|
||||
switch (calendarEventSpec.type) {
|
||||
case CalendarEventSpec.TYPE_SUNRISE:
|
||||
iconId = PebbleIconID.SUNRISE;
|
||||
@ -523,9 +526,11 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
break;
|
||||
default:
|
||||
iconId = PebbleIconID.TIMELINE_CALENDAR;
|
||||
attributes.add(new Pair<>(3, (Object) calendarEventSpec.description));
|
||||
attributes.add(new Pair<>(11, (Object) calendarEventSpec.location));
|
||||
}
|
||||
|
||||
return encodeTimelinePin(new UUID(GB_UUID_MASK | calendarEventSpec.type, id), calendarEventSpec.timestamp, (short) calendarEventSpec.durationInSeconds, iconId, calendarEventSpec.title, calendarEventSpec.description);
|
||||
return encodeTimelinePin(new UUID(GB_UUID_MASK | calendarEventSpec.type, id), calendarEventSpec.timestamp, (short) (calendarEventSpec.durationInSeconds / 60), iconId, attributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -838,17 +843,34 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
return buf.array();
|
||||
}
|
||||
|
||||
private byte[] encodeTimelinePin(UUID uuid, int timestamp, short duration, int icon_id, String title, String subtitle) {
|
||||
private byte[] encodeTimelinePin(UUID uuid, int timestamp, short duration, int icon_id, List<Pair<Integer, Object>> attributes) {
|
||||
final short TIMELINE_PIN_LENGTH = 46;
|
||||
|
||||
//FIXME: dont depend layout on icon :P
|
||||
byte layout_id = 0x01;
|
||||
if (icon_id == PebbleIconID.TIMELINE_CALENDAR) {
|
||||
layout_id = 0x02;
|
||||
}
|
||||
icon_id |= 0x80000000;
|
||||
byte attributes_count = 2;
|
||||
byte attributes_count = 1;
|
||||
byte actions_count = 0;
|
||||
|
||||
int attributes_length = 10 + title.getBytes().length;
|
||||
if (subtitle != null && !subtitle.isEmpty()) {
|
||||
attributes_length += 3 + subtitle.getBytes().length;
|
||||
attributes_count += 1;
|
||||
int attributes_length = 7;
|
||||
for (Pair<Integer, Object> pair : attributes) {
|
||||
if (pair.first == null || pair.second == null)
|
||||
continue;
|
||||
attributes_count++;
|
||||
if (pair.second instanceof Integer) {
|
||||
attributes_length += 7;
|
||||
} else if (pair.second instanceof Byte) {
|
||||
attributes_length += 4;
|
||||
} else if (pair.second instanceof String) {
|
||||
attributes_length += ((String) pair.second).getBytes().length + 3;
|
||||
} else if (pair.second instanceof byte[]) {
|
||||
attributes_length += ((byte[]) pair.second).length + 3;
|
||||
} else {
|
||||
LOG.warn("unsupported type for timeline attributes: " + pair.second.getClass().toString());
|
||||
}
|
||||
}
|
||||
|
||||
int pin_length = TIMELINE_PIN_LENGTH + attributes_length;
|
||||
@ -865,8 +887,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
buf.putShort(duration);
|
||||
buf.put((byte) 0x02); // type (0x02 = pin)
|
||||
buf.putShort((short) 0x0001); // flags 0x0001 = ?
|
||||
buf.put((byte) 0x01); // layout was (0x02 = pin?), 0x01 needed for subtitle but seems to do no harm if there isn't one
|
||||
|
||||
buf.put(layout_id); // layout was (0x02 = pin?), 0x01 needed for subtitle but seems to do no harm if there isn't one
|
||||
buf.putShort((short) attributes_length); // total length of all attributes and actions in bytes
|
||||
buf.put(attributes_count);
|
||||
buf.put(actions_count);
|
||||
@ -874,13 +895,24 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
buf.put((byte) 4); // icon
|
||||
buf.putShort((short) 4); // length of int
|
||||
buf.putInt(icon_id);
|
||||
buf.put((byte) 1); // title
|
||||
buf.putShort((short) title.getBytes().length);
|
||||
buf.put(title.getBytes());
|
||||
if (subtitle != null && !subtitle.isEmpty()) {
|
||||
buf.put((byte) 2); //subtitle
|
||||
buf.putShort((short) subtitle.getBytes().length);
|
||||
buf.put(subtitle.getBytes());
|
||||
|
||||
for (Pair<Integer, Object> pair : attributes) {
|
||||
if (pair.first == null || pair.second == null)
|
||||
continue;
|
||||
buf.put(pair.first.byteValue());
|
||||
if (pair.second instanceof Integer) {
|
||||
buf.putShort((short) 4);
|
||||
buf.putInt(((Integer) pair.second));
|
||||
} else if (pair.second instanceof Byte) {
|
||||
buf.putShort((short) 1);
|
||||
buf.put((Byte) pair.second);
|
||||
} else if (pair.second instanceof String) {
|
||||
buf.putShort((short) ((String) pair.second).getBytes().length);
|
||||
buf.put(((String) pair.second).getBytes());
|
||||
} else if (pair.second instanceof byte[]) {
|
||||
buf.putShort((short) ((byte[]) pair.second).length);
|
||||
buf.put((byte[]) pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
return encodeBlobdb(uuid, BLOBDB_INSERT, BLOBDB_PIN, buf.array());
|
||||
@ -2341,13 +2373,30 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
|
||||
GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes();
|
||||
if (command == 0x01) { //session setup
|
||||
sendBytes.encodedBytes = null;
|
||||
} else if (command == 0x02) { //dictation result
|
||||
int replLenght = 7;
|
||||
byte replStatus = 5; // 5 = disabled, change to 0 to send success
|
||||
ByteBuffer repl = ByteBuffer.allocate(LENGTH_PREFIX + replLenght);
|
||||
repl.order(ByteOrder.BIG_ENDIAN);
|
||||
repl.putShort((short) replLenght);
|
||||
repl.putShort(ENDPOINT_VOICECONTROL);
|
||||
repl.put(command);
|
||||
repl.putInt(flags);
|
||||
repl.put(session_type);
|
||||
repl.put(replStatus);
|
||||
|
||||
sendBytes.encodedBytes = repl.array();
|
||||
|
||||
} else if (command == 0x02) { //dictation result (possibly it is something we send, not something we receive)
|
||||
sendBytes.encodedBytes = null;
|
||||
}
|
||||
return sendBytes;
|
||||
}
|
||||
|
||||
private GBDeviceEvent decodeAudioStream(ByteBuffer buf) {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GBDeviceEvent[] decodeResponse(byte[] responseData) {
|
||||
ByteBuffer buf = ByteBuffer.wrap(responseData);
|
||||
@ -2607,11 +2656,13 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
case ENDPOINT_APPLOGS:
|
||||
decodeAppLogs(buf);
|
||||
break;
|
||||
// case ENDPOINT_VOICECONTROL:
|
||||
// devEvts = new GBDeviceEvent[]{decodeVoiceControl(buf)};
|
||||
// case ENDPOINT_AUDIOSTREAM:
|
||||
// LOG.debug(GB.hexdump(responseData, 0, responseData.length));
|
||||
// break;
|
||||
case ENDPOINT_VOICECONTROL:
|
||||
devEvts = new GBDeviceEvent[]{decodeVoiceControl(buf)};
|
||||
break;
|
||||
case ENDPOINT_AUDIOSTREAM:
|
||||
devEvts = new GBDeviceEvent[]{decodeAudioStream(buf)};
|
||||
// LOG.debug("AUDIOSTREAM DATA: " + GB.hexdump(responseData, 4, length));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -148,6 +148,7 @@ public class PebbleSupport extends AbstractSerialDeviceSupport {
|
||||
}
|
||||
}
|
||||
if (reconnect()) {
|
||||
super.onDeleteNotification(notificationSpec.id); //update notification hack
|
||||
super.onNotification(notificationSpec);
|
||||
}
|
||||
}
|
||||
|
@ -124,11 +124,6 @@ public class VibratissimoSupport extends AbstractBTLEDeviceSupport {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pair() {
|
||||
|
||||
}
|
||||
|
||||
private void handleDeviceInfo(nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo.DeviceInfo info) {
|
||||
LOG.warn("Device info: " + info);
|
||||
versionCmd.hwVersion = info.getHardwareRevision();
|
||||
|
@ -68,12 +68,6 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pair() {
|
||||
// Default implementation does no manual pairing, use the Android
|
||||
// pairing dialog instead.
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazily creates and returns the GBDeviceProtocol instance to be used.
|
||||
*/
|
||||
|
@ -71,16 +71,17 @@ public class CheckSums {
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] readAll(InputStream in, long maxLen) throws IOException {
|
||||
// copy&paste of FileUtils.readAll() to have it free from Android dependencies
|
||||
private static byte[] readAll(InputStream in, long maxLen) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(8192, in.available()));
|
||||
byte[] buf = new byte[8192];
|
||||
int read = 0;
|
||||
int read;
|
||||
long totalRead = 0;
|
||||
while ((read = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, read);
|
||||
totalRead += read;
|
||||
if (totalRead > maxLen) {
|
||||
throw new IOException("Too much data to read into memory. Got already " + totalRead + buf);
|
||||
throw new IOException("Too much data to read into memory. Got already " + totalRead);
|
||||
}
|
||||
}
|
||||
return out.toByteArray();
|
||||
|
@ -54,7 +54,7 @@ public class DateTimeUtils {
|
||||
DurationFormatter df = DurationFormatter.Builder.SYMBOLS
|
||||
.maximum(TimeUnit.DAYS)
|
||||
.minimum(TimeUnit.MINUTES)
|
||||
.suppressZeros(DurationFormatter.SuppressZeros.LEADING)
|
||||
.suppressZeros(DurationFormatter.SuppressZeros.LEADING, DurationFormatter.SuppressZeros.TRAILING)
|
||||
.maximumAmountOfUnitsToShow(2)
|
||||
.build();
|
||||
return df.format(duration, unit);
|
||||
|
@ -38,6 +38,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBEnvironment;
|
||||
|
||||
public class FileUtils {
|
||||
// Don't use slf4j here -- would be a bootstrapping problem
|
||||
@ -54,7 +55,9 @@ public class FileUtils {
|
||||
if (!sourceFile.exists()) {
|
||||
throw new IOException("Does not exist: " + sourceFile.getAbsolutePath());
|
||||
}
|
||||
copyFile(new FileInputStream(sourceFile), new FileOutputStream(destFile));
|
||||
try (FileInputStream in = new FileInputStream(sourceFile); FileOutputStream out = new FileOutputStream(destFile)) {
|
||||
copyFile(in, out);
|
||||
}
|
||||
}
|
||||
|
||||
private static void copyFile(FileInputStream sourceStream, FileOutputStream destStream) throws IOException {
|
||||
@ -207,10 +210,12 @@ public class FileUtils {
|
||||
|
||||
// the first directory is also the primary external storage, i.e. the same as Environment.getExternalFilesDir()
|
||||
// TODO: check the mount state of *all* dirs when switching to later API level
|
||||
if (!GBEnvironment.env().isLocalTest()) { // don't do this with robolectric
|
||||
if (i == 0 && !Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
|
||||
GB.log("ignoring unmounted external storage dir: " + dir, GB.INFO, null);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.add(dir); // add last
|
||||
}
|
||||
return result;
|
||||
@ -229,13 +234,13 @@ public class FileUtils {
|
||||
public static byte[] readAll(InputStream in, long maxLen) throws IOException {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(Math.max(8192, in.available()));
|
||||
byte[] buf = new byte[8192];
|
||||
int read = 0;
|
||||
int read;
|
||||
long totalRead = 0;
|
||||
while ((read = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, read);
|
||||
totalRead += read;
|
||||
if (totalRead > maxLen) {
|
||||
throw new IOException("Too much data to read into memory. Got already " + totalRead + buf);
|
||||
throw new IOException("Too much data to read into memory. Got already " + totalRead);
|
||||
}
|
||||
}
|
||||
return out.toByteArray();
|
||||
|
@ -60,12 +60,8 @@ public class GB {
|
||||
public static final String DISPLAY_MESSAGE_MESSAGE = "message";
|
||||
public static final String DISPLAY_MESSAGE_DURATION = "duration";
|
||||
public static final String DISPLAY_MESSAGE_SEVERITY = "severity";
|
||||
public static GBEnvironment environment;
|
||||
|
||||
public static Notification createNotification(String text, boolean connected, Context context) {
|
||||
if (env().isLocalTest()) {
|
||||
return null;
|
||||
}
|
||||
Intent notificationIntent = new Intent(context, ControlCenterv2.class);
|
||||
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
@ -80,7 +76,7 @@ public class GB {
|
||||
.setContentIntent(pendingIntent)
|
||||
.setOngoing(true);
|
||||
if (GBApplication.isRunningLollipopOrLater()) {
|
||||
builder.setVisibility(Notification.VISIBILITY_PUBLIC);
|
||||
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
|
||||
}
|
||||
if (GBApplication.minimizeNotification()) {
|
||||
builder.setPriority(Notification.PRIORITY_MIN);
|
||||
@ -227,7 +223,7 @@ public class GB {
|
||||
*/
|
||||
public static void toast(final Context context, final String message, final int displayTime, final int severity, final Throwable ex) {
|
||||
log(message, severity, ex); // log immediately, not delayed
|
||||
if (env().isLocalTest()) {
|
||||
if (GBEnvironment.env().isLocalTest()) {
|
||||
return;
|
||||
}
|
||||
Looper mainLooper = Looper.getMainLooper();
|
||||
@ -272,7 +268,7 @@ public class GB {
|
||||
notificationIntent, 0);
|
||||
|
||||
NotificationCompat.Builder nb = new NotificationCompat.Builder(context)
|
||||
.setVisibility(Notification.VISIBILITY_PUBLIC)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentTitle(context.getString(R.string.app_name))
|
||||
.setContentText(text)
|
||||
.setContentIntent(pendingIntent)
|
||||
@ -358,15 +354,15 @@ public class GB {
|
||||
}
|
||||
|
||||
public static void updateBatteryNotification(String text, String bigText, Context context) {
|
||||
if (env().isLocalTest()) {
|
||||
if (GBEnvironment.env().isLocalTest()) {
|
||||
return;
|
||||
}
|
||||
Notification notification = createBatteryNotification(text, bigText, context);
|
||||
updateNotification(notification, NOTIFICATION_ID_LOW_BATTERY, context);
|
||||
}
|
||||
|
||||
public static GBEnvironment env() {
|
||||
return environment;
|
||||
public static void removeBatteryNotification(Context context) {
|
||||
removeNotification(NOTIFICATION_ID_LOW_BATTERY, context);
|
||||
}
|
||||
|
||||
public static void assertThat(boolean condition, String errorMessage) {
|
||||
|
@ -20,7 +20,7 @@ import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
|
||||
public class GBPrefs {
|
||||
|
||||
public static final String PACKAGE_BLACKLIST = "package_blacklist";
|
||||
public static final String AUTO_RECONNECT = "general_autocreconnect";
|
||||
private static final String AUTO_START = "general_autostartonboot";
|
||||
private static final boolean AUTO_START_DEFAULT = true;
|
||||
@ -61,7 +61,7 @@ public class GBPrefs {
|
||||
}
|
||||
}
|
||||
|
||||
public int getUserSex() {
|
||||
public int getUserGender() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,147 @@
|
||||
/* Copyright (C) 2017 Alberto, Carsten Pfeiffer
|
||||
|
||||
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.util;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import android.content.SharedPreferences;
|
||||
import android.util.Xml;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
|
||||
public class ImportExportSharedPreferences {
|
||||
|
||||
private static final String BOOLEAN = Boolean.class.getSimpleName();
|
||||
private static final String FLOAT = Float.class.getSimpleName();
|
||||
private static final String INTEGER = Integer.class.getSimpleName();
|
||||
private static final String LONG = Long.class.getSimpleName();
|
||||
private static final String STRING = String.class.getSimpleName();
|
||||
private static final String HASHSET = HashSet.class.getSimpleName();
|
||||
|
||||
private static final String NAME = "name";
|
||||
private static final String PREFERENCES = "preferences";
|
||||
|
||||
public static void exportToFile(SharedPreferences sharedPreferences, File outFile,
|
||||
Set<String> doNotExport) throws IOException {
|
||||
export(sharedPreferences, new FileWriter(outFile), doNotExport);
|
||||
}
|
||||
|
||||
|
||||
public static void export(SharedPreferences sharedPreferences, Writer writer,
|
||||
Set<String> doNotExport) throws IOException {
|
||||
XmlSerializer serializer = Xml.newSerializer();
|
||||
serializer.setOutput(writer);
|
||||
serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
|
||||
serializer.startDocument("UTF-8", true);
|
||||
serializer.startTag("", PREFERENCES);
|
||||
for (Map.Entry<String, ?> entry : sharedPreferences.getAll().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (doNotExport != null && doNotExport.contains(key)) continue;
|
||||
|
||||
Object valueObject = entry.getValue();
|
||||
// Skip this entry if the value is null;
|
||||
if (valueObject == null) continue;
|
||||
|
||||
String valueType = valueObject.getClass().getSimpleName();
|
||||
String value = valueObject.toString();
|
||||
serializer.startTag("", valueType);
|
||||
serializer.attribute("", NAME, key);
|
||||
serializer.text(value);
|
||||
serializer.endTag("", valueType);
|
||||
|
||||
}
|
||||
serializer.endTag("", PREFERENCES);
|
||||
serializer.endDocument();
|
||||
writer.close();
|
||||
}
|
||||
|
||||
public static boolean importFromFile(SharedPreferences sharedPreferences, File inFile)
|
||||
throws Exception {
|
||||
return importFromReader(sharedPreferences, new FileReader(inFile));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param sharedPreferences
|
||||
* @param in
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
public static boolean importFromReader(SharedPreferences sharedPreferences, Reader in)
|
||||
throws Exception {
|
||||
SharedPreferences.Editor editor = sharedPreferences.edit();
|
||||
editor.clear();
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
parser.setInput(in);
|
||||
int eventType = parser.getEventType();
|
||||
String name = null;
|
||||
String key = null;
|
||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
||||
switch (eventType) {
|
||||
case XmlPullParser.START_TAG:
|
||||
name = parser.getName();
|
||||
key = parser.getAttributeValue("", NAME);
|
||||
break;
|
||||
case XmlPullParser.TEXT:
|
||||
// The parser is reading text outside an element if name is null,
|
||||
// so simply ignore this text part (which is usually something like '\n')
|
||||
if (name == null) break;
|
||||
String text = parser.getText();
|
||||
if (BOOLEAN.equals(name)) {
|
||||
editor.putBoolean(key, Boolean.parseBoolean(text));
|
||||
} else if (FLOAT.equals(name)) {
|
||||
editor.putFloat(key, Float.parseFloat(text));
|
||||
} else if (INTEGER.equals(name)) {
|
||||
editor.putInt(key, Integer.parseInt(text));
|
||||
} else if (LONG.equals(name)) {
|
||||
editor.putLong(key, Long.parseLong(text));
|
||||
} else if (STRING.equals(name)) {
|
||||
editor.putString(key, text);
|
||||
} else if (HASHSET.equals(name)) {
|
||||
if (key.equals(GBPrefs.PACKAGE_BLACKLIST)) {
|
||||
Set<String> blacklist = new HashSet<>();
|
||||
text=text.replace("[","").replace("]","");
|
||||
for (int z=0;z<text.split(",").length;z++){
|
||||
blacklist.add(text.split(",")[z].trim());
|
||||
}
|
||||
GBApplication.setBlackList(blacklist);
|
||||
}
|
||||
} else if (!PREFERENCES.equals(name)) {
|
||||
throw new Exception("Unkown type " + name);
|
||||
}
|
||||
break;
|
||||
case XmlPullParser.END_TAG:
|
||||
name = null;
|
||||
break;
|
||||
}
|
||||
eventType = parser.next();
|
||||
}
|
||||
return editor.commit();
|
||||
}
|
||||
}
|
@ -79,6 +79,7 @@ public class UriHelper {
|
||||
* Opens a stream to read the contents of the uri.
|
||||
* Note: the caller has to close the stream after usage.
|
||||
* Every invocation of this method will open a new stream.
|
||||
* FIXME: make sure that every caller actually closes the returned stream!
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
@NonNull
|
||||
@ -127,6 +128,7 @@ public class UriHelper {
|
||||
if (cursor == null) {
|
||||
throw new IOException("Unable to query metadata for: " + uri);
|
||||
}
|
||||
try {
|
||||
if (cursor.moveToFirst()) {
|
||||
int name_index = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME);
|
||||
if (name_index == -1) {
|
||||
@ -149,6 +151,9 @@ public class UriHelper {
|
||||
throw new IOException("Unable to retrieve metadata for: " + uri + ": " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
} else if (ContentResolver.SCHEME_FILE.equals(uriScheme)) {
|
||||
file = new File(uri.getPath());
|
||||
if (!file.exists()) {
|
||||
|
@ -61,4 +61,9 @@ public class Version implements Comparable<Version> {
|
||||
return false;
|
||||
return this.compareTo((Version) that) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return version.hashCode();
|
||||
}
|
||||
}
|
5
app/src/main/res/color/alarm_dow.xml
Normal file
5
app/src/main/res/color/alarm_dow.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:color="?attr/colorAccent" android:state_checked="true" />
|
||||
<item android:color="?attr/textColorPrimary" />
|
||||
</selector>
|
9
app/src/main/res/drawable/ic_add.xml
Normal file
9
app/src/main/res/drawable/ic_add.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
|
||||
</vector>
|
Binary file not shown.
Before Width: | Height: | Size: 201 B |
Binary file not shown.
Before Width: | Height: | Size: 168 B |
@ -1,214 +1,114 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.AlarmDetails">
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp">
|
||||
|
||||
<CheckBox
|
||||
<android.support.v7.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/alarm_cb_smart_wakeup"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/alarm_cb_smart_wakeup"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:drawableStart="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
android:text="@string/alarm_smart_wakeup"
|
||||
android:id="@+id/alarm_label_smart_wakeup"
|
||||
android:labelFor="@id/alarm_cb_smart_wakeup" />
|
||||
</LinearLayout>
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
<TimePicker
|
||||
android:id="@+id/alarm_time_picker"
|
||||
android:timePickerMode="clock"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="20dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="horizontal"
|
||||
android:id="@+id/dowSelector"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:baselineAligned="false"
|
||||
android:id="@+id/dowSelector">
|
||||
android:orientation="horizontal">
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<CheckBox
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/alarm_cb_mon"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:gravity="center_horizontal|bottom"/>
|
||||
|
||||
<TextView
|
||||
<android.support.v7.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/alarm_cb_monday"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_weight="1"
|
||||
android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
android:text="@string/alarm_mon_short"
|
||||
android:id="@+id/alarm_label_cb_mon"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:labelFor="@id/alarm_cb_mon"
|
||||
android:gravity="center_horizontal|top"/>
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
|
||||
<CheckBox
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/alarm_cb_tue"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:gravity="center_horizontal|bottom" />
|
||||
|
||||
<TextView
|
||||
<android.support.v7.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/alarm_cb_tuesday"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_weight="1"
|
||||
android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
android:text="@string/alarm_tue_short"
|
||||
android:id="@+id/alarm_label_cb_tue"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:labelFor="@id/alarm_cb_tue"
|
||||
android:gravity="center_horizontal|top" />
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
|
||||
<CheckBox
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/alarm_cb_wed"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:gravity="center_horizontal|bottom"/>
|
||||
|
||||
<TextView
|
||||
<android.support.v7.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/alarm_cb_wednesday"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_weight="1"
|
||||
android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
android:text="@string/alarm_wed_short"
|
||||
android:id="@+id/alarm_label_cb_wed"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:labelFor="@id/alarm_cb_wed"
|
||||
android:gravity="center_horizontal|top" />
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
|
||||
<CheckBox
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/alarm_cb_thu"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:gravity="center_horizontal|bottom" />
|
||||
|
||||
<TextView
|
||||
<android.support.v7.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/alarm_cb_thursday"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_weight="1"
|
||||
android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
android:text="@string/alarm_thu_short"
|
||||
android:id="@+id/alarm_label_cb_thu"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:labelFor="@id/alarm_cb_thu"
|
||||
android:gravity="center_horizontal|top" />
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
|
||||
<CheckBox
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/alarm_cb_fri"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:gravity="center_horizontal|bottom"/>
|
||||
|
||||
<TextView
|
||||
<android.support.v7.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/alarm_cb_friday"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_weight="1"
|
||||
android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
android:text="@string/alarm_fri_short"
|
||||
android:id="@+id/alarm_label_cb_fri"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:labelFor="@id/alarm_cb_fri"
|
||||
android:gravity="center_horizontal|top" />
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
|
||||
<CheckBox
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/alarm_cb_sat"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:gravity="center_horizontal|bottom"/>
|
||||
|
||||
<TextView
|
||||
<android.support.v7.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/alarm_cb_saturday"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_weight="1"
|
||||
android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
android:text="@string/alarm_sat_short"
|
||||
android:id="@+id/alarm_label_cb_sat"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:labelFor="@id/alarm_cb_sat"
|
||||
android:gravity="center_horizontal|top" />
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1">
|
||||
|
||||
<CheckBox
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/alarm_cb_sun"
|
||||
android:layout_gravity="center_horizontal"/>
|
||||
|
||||
<TextView
|
||||
<android.support.v7.widget.AppCompatCheckedTextView
|
||||
android:id="@+id/alarm_cb_sunday"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_weight="1"
|
||||
android:drawableTop="?android:attr/listChoiceIndicatorMultiple"
|
||||
android:gravity="center"
|
||||
android:text="@string/alarm_sun_short"
|
||||
android:id="@+id/alarm_label_cb_sun"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:labelFor="@id/alarm_cb_sun"
|
||||
android:gravity="center_horizontal|top" />
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
@ -1,12 +1,16 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.AndroidPairingActivity">
|
||||
|
||||
<TextView android:text="@string/android_pairing_hint" android:layout_width="wrap_content"
|
||||
<TextView
|
||||
android:text="@string/android_pairing_hint"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
@ -1,16 +1,25 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.AppBlacklistActivity">
|
||||
|
||||
<ListView
|
||||
android:layout_width="wrap_content"
|
||||
<android.support.v7.widget.SearchView
|
||||
android:id="@+id/appListViewSearch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/appListView"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true" />
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingStart="16dp">
|
||||
|
||||
</android.support.v7.widget.SearchView>
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/appListView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/appListViewSearch"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:divider="@null" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
@ -14,8 +14,7 @@
|
||||
android:id="@+id/itemListView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="false">
|
||||
</ListView>
|
||||
android:layout_alignParentEnd="false"></ListView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/infoTextView"
|
||||
@ -67,8 +66,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/installButton"
|
||||
android:layout_alignParentEnd="false">
|
||||
</ListView>
|
||||
android:layout_alignParentEnd="false"></ListView>
|
||||
|
||||
<android.widget.Space
|
||||
android:layout_width="wrap_content"
|
||||
|
@ -7,5 +7,8 @@
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/appListView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="fill_parent" />
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:divider="@null" />
|
||||
</RelativeLayout>
|
||||
|
@ -1,36 +1,52 @@
|
||||
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/activity_swipe_layout"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity"
|
||||
android:paddingLeft="0px"
|
||||
android:paddingRight="0px"
|
||||
android:paddingTop="0px"
|
||||
android:paddingBottom="0px"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity">
|
||||
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:orientation="vertical"
|
||||
<LinearLayout
|
||||
android:id="@+id/charts_main_layout"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.v4.view.ViewPager
|
||||
android:id="@+id/charts_pager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity">
|
||||
|
||||
<android.support.design.widget.TabLayout
|
||||
android:id="@+id/charts_pagerTabStrip"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top"
|
||||
app:tabMode="scrollable" />
|
||||
</android.support.v4.view.ViewPager>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/charts_date_bar"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="fill_horizontal"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/charts_previous"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="<" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/charts_text_date"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="5"
|
||||
android:text="Today"
|
||||
/>
|
||||
android:gravity="center_horizontal"
|
||||
android:layout_weight="1"
|
||||
android:text="Today" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/charts_next"
|
||||
@ -39,18 +55,5 @@
|
||||
android:text=">" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<android.support.v4.view.ViewPager android:id="@+id/charts_pager"
|
||||
android:layout_width="match_parent" android:layout_height="match_parent"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity">
|
||||
<android.support.v4.view.PagerTabStrip
|
||||
android:id="@+id/charts_pagerTabStrip"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom" />
|
||||
</android.support.v4.view.ViewPager>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</android.support.v4.widget.SwipeRefreshLayout>
|
||||
|
@ -1,12 +1,12 @@
|
||||
<?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:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/charts_duration_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
</TextView>
|
||||
android:layout_height="wrap_content"></TextView>
|
||||
|
||||
</LinearLayout>
|
@ -1,14 +1,17 @@
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms">
|
||||
|
||||
<ListView
|
||||
android:descendantFocusability="blocksDescendants"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:divider="@null"
|
||||
android:id="@+id/alarm_list" />
|
||||
</FrameLayout>
|
||||
</RelativeLayout>
|
||||
|
@ -30,11 +30,11 @@
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:src="@drawable/ic_add_white"
|
||||
app:srcCompat="@drawable/ic_add"
|
||||
app:elevation="6dp"
|
||||
app:pressedTranslationZ="12dp"
|
||||
android:layout_marginBottom="30dp"
|
||||
android:layout_marginRight="10dp"/>
|
||||
android:layout_marginEnd="10dp" />
|
||||
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
|
@ -1,6 +1,8 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
|
@ -3,14 +3,10 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:paddingBottom="0px"
|
||||
android:paddingLeft="0px"
|
||||
android:paddingRight="0px"
|
||||
android:paddingTop="0px"
|
||||
tools:context=".activities.appmanager.AppManagerActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/charts_main_layout"
|
||||
android:id="@+id/appmanager_main_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
@ -21,14 +17,15 @@
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".activities.appmanager.AppManagerActivity">
|
||||
|
||||
<android.support.v4.view.PagerTabStrip
|
||||
<android.support.design.widget.TabLayout
|
||||
android:id="@+id/charts_pagerTabStrip"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom" />
|
||||
android:layout_gravity="top" />
|
||||
</android.support.v4.view.ViewPager>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
@ -36,10 +33,10 @@
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:src="@drawable/ic_add_white"
|
||||
app:srcCompat="@drawable/ic_add"
|
||||
app:elevation="6dp"
|
||||
app:pressedTranslationZ="12dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:layout_marginRight="10dp" />
|
||||
android:layout_marginBottom="30dp"
|
||||
android:layout_marginEnd="10dp" />
|
||||
|
||||
</android.widget.RelativeLayout>
|
||||
|
@ -1,12 +1,16 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPairingActivity">
|
||||
|
||||
<TextView android:text="@string/pairing" android:layout_width="wrap_content"
|
||||
<TextView
|
||||
android:text="@string/pairing"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/miband_pair_message" />
|
||||
|
||||
|
@ -1,12 +1,16 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebblePairingActivity">
|
||||
|
||||
<TextView android:text="@string/pairing" android:layout_width="wrap_content"
|
||||
<TextView
|
||||
android:text="@string/pairing"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/pebble_pair_message" />
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user