mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-12-28 19:45:50 +01:00
Merge branch 'db-refactoring'
This commit is contained in:
commit
8772631087
2
GBDaoGenerator/.gitignore
vendored
Normal file
2
GBDaoGenerator/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/bin
|
||||
/build
|
31
GBDaoGenerator/build.gradle
Normal file
31
GBDaoGenerator/build.gradle
Normal file
@ -0,0 +1,31 @@
|
||||
apply plugin: 'java'
|
||||
//apply plugin: 'maven'
|
||||
apply plugin:'application'
|
||||
|
||||
archivesBaseName = 'gadgetbridge-daogenerator'
|
||||
//version = '0.9.2-SNAPSHOT'
|
||||
|
||||
dependencies {
|
||||
// compile 'org.greenrobot:greendao-generator:2.2.0'
|
||||
compile 'com.github.freeyourgadget:greendao:c3830951e5dd3d1e63d7bac600d5f773b81df363'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
java {
|
||||
srcDir 'src'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mainClassName = "nodomain.freeyourgadget.gadgetbridge.daogen.GBDaoGenerator"
|
||||
|
||||
task genSources(type: JavaExec) {
|
||||
main = mainClassName
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
workingDir = '../'
|
||||
}
|
||||
|
||||
artifacts {
|
||||
archives jar
|
||||
}
|
@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright (C) 2011 Markus Junginger, greenrobot (http://greenrobot.de)
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* Generates entities and DAOs for the example project DaoExample.
|
||||
* Automatically run during build.
|
||||
*/
|
||||
public class GBDaoGenerator {
|
||||
|
||||
public static final String VALID_FROM_UTC = "validFromUTC";
|
||||
public static final String VALID_TO_UTC = "validToUTC";
|
||||
private static final String MAIN_PACKAGE = "nodomain.freeyourgadget.gadgetbridge";
|
||||
private static final String MODEL_PACKAGE = MAIN_PACKAGE + ".model";
|
||||
private static final String VALID_BY_DATE = MODEL_PACKAGE + ".ValidByDate";
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Schema schema = new Schema(7, MAIN_PACKAGE + ".entities");
|
||||
|
||||
addActivityDescription(schema);
|
||||
|
||||
Entity userAttributes = addUserAttributes(schema);
|
||||
Entity user = addUserInfo(schema, userAttributes);
|
||||
|
||||
Entity deviceAttributes = addDeviceAttributes(schema);
|
||||
Entity device = addDevice(schema, deviceAttributes);
|
||||
|
||||
addMiBandActivitySample(schema, user, device);
|
||||
addPebbleActivitySample(schema, user, device);
|
||||
|
||||
new DaoGenerator().generateAll(schema, "app/src/main/java");
|
||||
}
|
||||
|
||||
private static Entity addActivityDescription(Schema schema) {
|
||||
Entity activityDescription = addEntity(schema, "ActivityDescription");
|
||||
activityDescription.addIdProperty();
|
||||
activityDescription.addIntProperty("fromTimestamp").notNull();
|
||||
activityDescription.addIntProperty("toTimestamp");
|
||||
return activityDescription;
|
||||
}
|
||||
|
||||
private static Entity addUserInfo(Schema schema, Entity userAttributes) {
|
||||
Entity user = addEntity(schema, "User");
|
||||
user.addIdProperty();
|
||||
user.addStringProperty("name").notNull();
|
||||
user.addDateProperty("birthday").notNull();
|
||||
user.addIntProperty("gender").notNull();
|
||||
Property userId = userAttributes.addLongProperty("userId").notNull().getProperty();
|
||||
|
||||
// sorted by the from-date, newest first
|
||||
Property userAttributesSortProperty = getPropertyByName(userAttributes, VALID_FROM_UTC);
|
||||
user.addToMany(userAttributes, userId).orderDesc(userAttributesSortProperty);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
private static Property getPropertyByName(Entity entity, String propertyName) {
|
||||
for (Property prop : entity.getProperties()) {
|
||||
if (propertyName.equals(prop.getPropertyName())) {
|
||||
return prop;
|
||||
}
|
||||
}
|
||||
throw new IllegalStateException("Could not find property " + propertyName + " in entity " + entity.getClassName());
|
||||
}
|
||||
|
||||
private static Entity addUserAttributes(Schema schema) {
|
||||
// additional properties of a user, which may change during the lifetime of a user
|
||||
// this allows changing attributes while preserving user identity
|
||||
Entity userAttributes = addEntity(schema, "UserAttributes");
|
||||
userAttributes.addIdProperty();
|
||||
userAttributes.addIntProperty("heightCM").notNull();
|
||||
userAttributes.addIntProperty("weightKG").notNull();
|
||||
userAttributes.addIntProperty("sleepGoalHPD");
|
||||
userAttributes.addIntProperty("stepsGoalSPD");
|
||||
addDateValidityTo(userAttributes);
|
||||
|
||||
return userAttributes;
|
||||
}
|
||||
|
||||
private static void addDateValidityTo(Entity entity) {
|
||||
entity.addDateProperty(VALID_FROM_UTC);
|
||||
entity.addDateProperty(VALID_TO_UTC);
|
||||
|
||||
entity.implementsInterface(VALID_BY_DATE);
|
||||
}
|
||||
|
||||
private static Entity addDevice(Schema schema, Entity deviceAttributes) {
|
||||
Entity device = addEntity(schema, "Device");
|
||||
device.addIdProperty();
|
||||
device.addStringProperty("name").notNull();
|
||||
device.addStringProperty("manufacturer").notNull();
|
||||
device.addStringProperty("identifier").notNull().unique().javaDocGetterAndSetter("The fixed identifier, i.e. MAC address of the device.");
|
||||
Property deviceId = deviceAttributes.addLongProperty("deviceId").notNull().getProperty();
|
||||
// sorted by the from-date, newest first
|
||||
Property deviceAttributesSortProperty = getPropertyByName(deviceAttributes, VALID_FROM_UTC);
|
||||
device.addToMany(deviceAttributes, deviceId).orderDesc(deviceAttributesSortProperty);
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
private static Entity addDeviceAttributes(Schema schema) {
|
||||
Entity deviceAttributes = addEntity(schema, "DeviceAttributes");
|
||||
deviceAttributes.addIdProperty();
|
||||
deviceAttributes.addStringProperty("firmwareVersion1").notNull();
|
||||
deviceAttributes.addStringProperty("firmwareVersion2");
|
||||
addDateValidityTo(deviceAttributes);
|
||||
|
||||
return deviceAttributes;
|
||||
}
|
||||
|
||||
private static Entity addMiBandActivitySample(Schema schema, Entity user, Entity device) {
|
||||
// public GBActivitySample(SampleProvider provider, int timestamp, int intensity, int steps, int type, int customValue) {
|
||||
Entity activitySample = addEntity(schema, "MiBandActivitySample");
|
||||
addCommonActivitySampleProperties(schema, activitySample, user, device);
|
||||
addHeartRateProperties(activitySample);
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static void addHeartRateProperties(Entity activitySample) {
|
||||
activitySample.addImport(MODEL_PACKAGE + ".HeartRateSample");
|
||||
activitySample.implementsInterface("HeartRateSample");
|
||||
activitySample.addIntProperty("heartRate");
|
||||
}
|
||||
|
||||
private static Entity addPebbleActivitySample(Schema schema, Entity user, Entity device) {
|
||||
// public GBActivitySample(SampleProvider provider, int timestamp, int intensity, int steps, int type, int customValue) {
|
||||
Entity activitySample = addEntity(schema, "PebbleActivitySample");
|
||||
addCommonActivitySampleProperties(schema, activitySample, user, device);
|
||||
return activitySample;
|
||||
}
|
||||
|
||||
private static void addCommonActivitySampleProperties(Schema schema, Entity activitySample, Entity user, Entity device) {
|
||||
activitySample.setSuperclass("AbstractActivitySample");
|
||||
activitySample.addImport(MODEL_PACKAGE + ".ActivitySample");
|
||||
activitySample.addImport(MAIN_PACKAGE + ".devices.SampleProvider");
|
||||
activitySample.implementsInterface("ActivitySample");
|
||||
activitySample.setJavaDoc(
|
||||
"This class represents a sample specific to the device. Values like activity kind or\n" +
|
||||
"intensity, are device specific. Normalized values can be retrieved through the\n" +
|
||||
"corresponding {@link SampleProvider}.");
|
||||
activitySample.addIdProperty();
|
||||
Property timestamp = activitySample.addIntProperty("timestamp").notNull().getProperty();
|
||||
activitySample.addIntProperty("rawIntensity").notNull();
|
||||
activitySample.addIntProperty("steps").notNull();
|
||||
activitySample.addIntProperty("rawKind").notNull();
|
||||
Property userId = activitySample.addLongProperty("userId").getProperty();
|
||||
activitySample.addToOne(user, userId);
|
||||
Property deviceId = activitySample.addLongProperty("deviceId").getProperty();
|
||||
activitySample.addToOne(device, deviceId);
|
||||
|
||||
Index indexUnique = new Index();
|
||||
indexUnique.addProperty(timestamp);
|
||||
indexUnique.addProperty(deviceId);
|
||||
indexUnique.makeUnique();
|
||||
activitySample.addIndex(indexUnique);
|
||||
}
|
||||
|
||||
private static Entity addEntity(Schema schema, String className) {
|
||||
Entity entity = schema.addEntity(className);
|
||||
entity.addImport("de.greenrobot.dao.AbstractDao");
|
||||
return entity;
|
||||
}
|
||||
}
|
@ -59,9 +59,15 @@ dependencies {
|
||||
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:c3830951e5dd3d1e63d7bac600d5f773b81df363'
|
||||
compile 'com.github.woxthebox:draglistview:1.2.6'
|
||||
}
|
||||
|
||||
preBuild.dependsOn(":GBDaoGenerator:genSources")
|
||||
gradle.beforeProject {
|
||||
preBuild.dependsOn(":GBDaoGenerator:genSources")
|
||||
}
|
||||
|
||||
check.dependsOn 'findbugs', 'pmd', 'lint'
|
||||
|
||||
task pmd(type: Pmd) {
|
||||
|
@ -216,10 +216,15 @@
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<!--
|
||||
forcing the DebugActivity to portrait mode avoids crashes with the progress
|
||||
dialog when changing orientation
|
||||
-->
|
||||
<activity
|
||||
android:name=".activities.DebugActivity"
|
||||
android:label="@string/title_activity_debug"
|
||||
android:parentActivityName=".activities.ControlCenter"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name=".activities.DiscoveryActivity"
|
||||
|
@ -11,6 +11,7 @@ import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION;
|
||||
@ -27,9 +28,11 @@ import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.ActivityDatabaseHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBConstants;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBOpenHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
@ -49,7 +52,6 @@ public class GBApplication extends Application {
|
||||
// Since this class must not log to slf4j, we use plain android.util.Log
|
||||
private static final String TAG = "GBApplication";
|
||||
private static GBApplication context;
|
||||
private static ActivityDatabaseHandler mActivityDatabaseHandler;
|
||||
private static final Lock dbLock = new ReentrantLock();
|
||||
private static DeviceService deviceService;
|
||||
private static SharedPreferences sharedPrefs;
|
||||
@ -59,6 +61,7 @@ public class GBApplication extends Application {
|
||||
private static LimitedQueue mIDSenderLookup = new LimitedQueue(16);
|
||||
private static Prefs prefs;
|
||||
private static GBPrefs gbPrefs;
|
||||
private static LockHandler lockHandler;
|
||||
/**
|
||||
* Note: is null on Lollipop and Kitkat
|
||||
*/
|
||||
@ -73,6 +76,7 @@ public class GBApplication extends Application {
|
||||
return dir.getAbsolutePath();
|
||||
}
|
||||
};
|
||||
private static DeviceManager deviceManager;
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
@ -116,9 +120,13 @@ public class GBApplication extends Application {
|
||||
|
||||
setupExceptionHandler();
|
||||
|
||||
setupDatabase(this);
|
||||
|
||||
deviceManager = new DeviceManager(this);
|
||||
|
||||
deviceService = createDeviceService();
|
||||
GB.environment = GBEnvironment.createDeviceEnvironment();
|
||||
mActivityDatabaseHandler = new ActivityDatabaseHandler(context);
|
||||
// mActivityDatabaseHandler = new ActivityDatabaseHandler(context);
|
||||
loadBlackList();
|
||||
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
@ -147,6 +155,17 @@ public class GBApplication extends Application {
|
||||
return prefs.getBoolean("log_to_file", false);
|
||||
}
|
||||
|
||||
static void setupDatabase(Context context) {
|
||||
// DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "test-db", null);
|
||||
DBOpenHelper helper = new DBOpenHelper(context, "test-db4", null);
|
||||
SQLiteDatabase db = helper.getWritableDatabase();
|
||||
DaoMaster daoMaster = new DaoMaster(db);
|
||||
if (lockHandler == null) {
|
||||
lockHandler = new LockHandler();
|
||||
}
|
||||
lockHandler.init(daoMaster, helper);
|
||||
}
|
||||
|
||||
public static Context getContext() {
|
||||
return context;
|
||||
}
|
||||
@ -167,6 +186,9 @@ public class GBApplication extends Application {
|
||||
* If acquiring was successful, callers must call #releaseDB when they
|
||||
* are done (from the same thread that acquired the lock!
|
||||
*
|
||||
* Callers must not hold a reference to the returned instance because it
|
||||
* will be invalidated at some point.
|
||||
*
|
||||
* @return the DBHandler
|
||||
* @throws GBException
|
||||
* @see #releaseDB()
|
||||
@ -174,7 +196,8 @@ public class GBApplication extends Application {
|
||||
public static DBHandler acquireDB() throws GBException {
|
||||
try {
|
||||
if (dbLock.tryLock(30, TimeUnit.SECONDS)) {
|
||||
return mActivityDatabaseHandler;
|
||||
return lockHandler;
|
||||
// return mActivityDatabaseHandler;
|
||||
}
|
||||
} catch (InterruptedException ex) {
|
||||
Log.i(TAG, "Interrupted while waiting for DB lock");
|
||||
@ -290,12 +313,16 @@ public class GBApplication extends Application {
|
||||
* @return true on successful deletion
|
||||
*/
|
||||
public static synchronized boolean deleteActivityDatabase() {
|
||||
if (mActivityDatabaseHandler != null) {
|
||||
mActivityDatabaseHandler.close();
|
||||
mActivityDatabaseHandler = null;
|
||||
// TODO: flush, close, reopen db
|
||||
if (lockHandler != null) {
|
||||
lockHandler.closeDb();
|
||||
}
|
||||
// if (mActivityDatabaseHandler != null) {
|
||||
// mActivityDatabaseHandler.close();
|
||||
// mActivityDatabaseHandler = null;
|
||||
// }
|
||||
boolean result = getContext().deleteDatabase(DBConstants.DATABASE_NAME);
|
||||
mActivityDatabaseHandler = new ActivityDatabaseHandler(getContext());
|
||||
// mActivityDatabaseHandler = new Activity7DatabaseHandler(getContext());
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -379,4 +406,8 @@ public class GBApplication extends Application {
|
||||
public static GBPrefs getGBPrefs() {
|
||||
return gbPrefs;
|
||||
}
|
||||
|
||||
public static DeviceManager getDeviceManager() {
|
||||
return deviceManager;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,101 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Provides lowlevel access to the database.
|
||||
*/
|
||||
public class LockHandler implements DBHandler {
|
||||
|
||||
private DaoMaster daoMaster = null;
|
||||
private DaoSession session = null;
|
||||
private SQLiteOpenHelper helper = null;
|
||||
|
||||
public LockHandler() {
|
||||
}
|
||||
|
||||
public void init(DaoMaster daoMaster, DBOpenHelper helper) {
|
||||
if (isValid()) {
|
||||
throw new IllegalStateException("DB must be closed before initializing it again");
|
||||
}
|
||||
if (daoMaster == null) {
|
||||
throw new IllegalArgumentException("daoMaster must not be null");
|
||||
}
|
||||
if (helper == null) {
|
||||
throw new IllegalArgumentException("helper must not be null");
|
||||
}
|
||||
this.daoMaster = daoMaster;
|
||||
this.helper = helper;
|
||||
|
||||
session = daoMaster.newSession();
|
||||
if (session == null) {
|
||||
throw new RuntimeException("Unable to create database session");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DaoMaster getDaoMaster() {
|
||||
return daoMaster;
|
||||
}
|
||||
|
||||
private boolean isValid() {
|
||||
return daoMaster != null;
|
||||
}
|
||||
|
||||
private void ensureValid() {
|
||||
if (!isValid()) {
|
||||
throw new IllegalStateException("LockHandler is not in a valid state");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
ensureValid();
|
||||
GBApplication.releaseDB();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void openDb() {
|
||||
if (session != null) {
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void closeDb() {
|
||||
if (session == null) {
|
||||
throw new IllegalStateException("session must not be null");
|
||||
}
|
||||
session.clear();
|
||||
session.getDatabase().close();
|
||||
session = null;
|
||||
helper = null;
|
||||
daoMaster = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQLiteOpenHelper getHelper() {
|
||||
ensureValid();
|
||||
return helper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DaoSession getDaoSession() {
|
||||
ensureValid();
|
||||
return session;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQLiteDatabase getDatabase() {
|
||||
ensureValid();
|
||||
return daoMaster.getDatabase();
|
||||
}
|
||||
}
|
@ -23,7 +23,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
/**
|
||||
* Implementation of SleepAlarmWidget functionality. When pressing the widget, an alarm will be set
|
||||
* to trigger after a predefined number of hours. A toast will confirm the user about this. The
|
||||
* value is retrieved using ActivityUser.().getActivityUserSleepDuration().
|
||||
* value is retrieved using ActivityUser.().getSleepDuration().
|
||||
*/
|
||||
public class SleepAlarmWidget extends AppWidgetProvider {
|
||||
|
||||
@ -71,7 +71,7 @@ public class SleepAlarmWidget extends AppWidgetProvider {
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
super.onReceive(context, intent);
|
||||
if (ACTION.equals(intent.getAction())) {
|
||||
int userSleepDuration = new ActivityUser().getActivityUserSleepDuration();
|
||||
int userSleepDuration = new ActivityUser().getSleepDuration();
|
||||
// current timestamp
|
||||
GregorianCalendar calendar = new GregorianCalendar();
|
||||
// add preferred sleep duration
|
||||
|
@ -4,7 +4,6 @@ import android.Manifest;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.Activity;
|
||||
import android.app.ProgressDialog;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
@ -33,7 +32,6 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import de.cketti.library.changelog.ChangeLog;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
@ -41,6 +39,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
@ -50,18 +49,17 @@ public class ControlCenter extends GBActivity {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ControlCenter.class);
|
||||
|
||||
public static final String ACTION_REFRESH_DEVICELIST
|
||||
= "nodomain.freeyourgadget.gadgetbridge.controlcenter.action.set_version";
|
||||
|
||||
private TextView hintTextView;
|
||||
private FloatingActionButton fab;
|
||||
private ImageView background;
|
||||
|
||||
private SwipeRefreshLayout swipeLayout;
|
||||
private GBDeviceAdapter mGBDeviceAdapter;
|
||||
private GBDevice selectedDevice = null;
|
||||
|
||||
private final List<GBDevice> deviceList = new ArrayList<>();
|
||||
private DeviceManager deviceManager;
|
||||
/**
|
||||
* Temporary field for the context menu
|
||||
*/
|
||||
private GBDevice selectedDevice;
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
@ -71,46 +69,16 @@ public class ControlCenter extends GBActivity {
|
||||
case GBApplication.ACTION_QUIT:
|
||||
finish();
|
||||
break;
|
||||
case ACTION_REFRESH_DEVICELIST:
|
||||
case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
|
||||
case DeviceManager.ACTION_DEVICES_CHANGED:
|
||||
refreshPairedDevices();
|
||||
break;
|
||||
case GBDevice.ACTION_DEVICE_CHANGED:
|
||||
GBDevice dev = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
if (dev.getAddress() != null) {
|
||||
int index = deviceList.indexOf(dev); // search by address
|
||||
if (index >= 0) {
|
||||
deviceList.set(index, dev);
|
||||
} else {
|
||||
deviceList.add(dev);
|
||||
}
|
||||
}
|
||||
updateSelectedDevice(dev);
|
||||
refreshPairedDevices();
|
||||
|
||||
refreshBusyState(dev);
|
||||
GBDevice selectedDevice = deviceManager.getSelectedDevice();
|
||||
refreshBusyState(selectedDevice);
|
||||
enableSwipeRefresh(selectedDevice);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void updateSelectedDevice(GBDevice dev) {
|
||||
if (selectedDevice == null) {
|
||||
selectedDevice = dev;
|
||||
} else {
|
||||
if (!selectedDevice.equals(dev)) {
|
||||
if (selectedDevice.isConnected() && dev.isConnected()) {
|
||||
LOG.warn("multiple connected devices -- this is currently not really supported");
|
||||
selectedDevice = dev; // use the last one that changed
|
||||
}
|
||||
if (!selectedDevice.isConnected()) {
|
||||
selectedDevice = dev; // use the last one that changed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshBusyState(GBDevice dev) {
|
||||
if (dev.isBusy()) {
|
||||
swipeLayout.setRefreshing(true);
|
||||
@ -120,7 +88,6 @@ public class ControlCenter extends GBActivity {
|
||||
swipeLayout.setRefreshing(false);
|
||||
}
|
||||
}
|
||||
mGBDeviceAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -128,6 +95,8 @@ public class ControlCenter extends GBActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_controlcenter);
|
||||
|
||||
deviceManager = GBApplication.getDeviceManager();
|
||||
|
||||
hintTextView = (TextView) findViewById(R.id.hintTextView);
|
||||
ListView deviceListView = (ListView) findViewById(R.id.deviceListView);
|
||||
fab = (FloatingActionButton) findViewById(R.id.fab);
|
||||
@ -140,12 +109,13 @@ public class ControlCenter extends GBActivity {
|
||||
}
|
||||
});
|
||||
|
||||
final List<GBDevice> deviceList = deviceManager.getDevices();
|
||||
mGBDeviceAdapter = new GBDeviceAdapter(this, deviceList);
|
||||
deviceListView.setAdapter(this.mGBDeviceAdapter);
|
||||
deviceListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView parent, View v, int position, long id) {
|
||||
GBDevice gbDevice = deviceList.get(position);
|
||||
GBDevice gbDevice = mGBDeviceAdapter.getItem(position);
|
||||
if (gbDevice.isInitialized()) {
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
|
||||
Class<? extends Activity> primaryActivity = coordinator.getPrimaryActivity();
|
||||
@ -155,7 +125,7 @@ public class ControlCenter extends GBActivity {
|
||||
startActivity(startIntent);
|
||||
}
|
||||
} else {
|
||||
GBApplication.deviceService().connect(deviceList.get(position));
|
||||
GBApplication.deviceService().connect(gbDevice);
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -172,13 +142,9 @@ public class ControlCenter extends GBActivity {
|
||||
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
filterLocal.addAction(GBApplication.ACTION_QUIT);
|
||||
filterLocal.addAction(ACTION_REFRESH_DEVICELIST);
|
||||
filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
|
||||
filterLocal.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
|
||||
filterLocal.addAction(DeviceManager.ACTION_DEVICES_CHANGED);
|
||||
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filterLocal);
|
||||
|
||||
registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
|
||||
|
||||
refreshPairedDevices();
|
||||
/*
|
||||
* Ask for permission to intercept notifications on first run.
|
||||
@ -200,7 +166,7 @@ public class ControlCenter extends GBActivity {
|
||||
|
||||
GBApplication.deviceService().start();
|
||||
|
||||
enableSwipeRefresh(selectedDevice);
|
||||
enableSwipeRefresh(deviceManager.getSelectedDevice());
|
||||
if (GB.isBluetoothEnabled() && deviceList.isEmpty() && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
startActivity(new Intent(this, DiscoveryActivity.class));
|
||||
} else {
|
||||
@ -212,7 +178,7 @@ public class ControlCenter extends GBActivity {
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
AdapterView.AdapterContextMenuInfo acmi = (AdapterView.AdapterContextMenuInfo) menuInfo;
|
||||
selectedDevice = deviceList.get(acmi.position);
|
||||
selectedDevice = mGBDeviceAdapter.getItem(acmi.position);
|
||||
if (selectedDevice != null && selectedDevice.isBusy()) {
|
||||
// no context menu when device is busy
|
||||
return;
|
||||
@ -254,6 +220,7 @@ public class ControlCenter extends GBActivity {
|
||||
}
|
||||
|
||||
private void fetchActivityData() {
|
||||
GBDevice selectedDevice = deviceManager.getSelectedDevice();
|
||||
if (selectedDevice == null) {
|
||||
return;
|
||||
}
|
||||
@ -359,22 +326,16 @@ public class ControlCenter extends GBActivity {
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
|
||||
unregisterReceiver(mReceiver);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
private void refreshPairedDevices() {
|
||||
Set<GBDevice> availableDevices = DeviceHelper.getInstance().getAvailableDevices(this);
|
||||
deviceList.retainAll(availableDevices);
|
||||
for (GBDevice availableDevice : availableDevices) {
|
||||
if (!deviceList.contains(availableDevice)) {
|
||||
deviceList.add(availableDevice);
|
||||
}
|
||||
}
|
||||
boolean connected = false;
|
||||
List<GBDevice> deviceList = deviceManager.getDevices();
|
||||
GBDevice connectedDevice = null;
|
||||
|
||||
for (GBDevice device : deviceList) {
|
||||
if (device.isConnected() || device.isConnecting()) {
|
||||
connected = true;
|
||||
connectedDevice = device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -385,8 +346,8 @@ public class ControlCenter extends GBActivity {
|
||||
background.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
if (connected) {
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(selectedDevice);
|
||||
if (connectedDevice != null) {
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(connectedDevice);
|
||||
hintTextView.setText(coordinator.getTapString());
|
||||
} else if (!deviceList.isEmpty()) {
|
||||
hintTextView.setText(R.string.tap_a_device_to_connect);
|
||||
|
@ -3,12 +3,14 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
@ -20,20 +22,20 @@ import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.e175.klaus.solarpositioning.DeltaT;
|
||||
import net.e175.klaus.solarpositioning.SPA;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.ActivityDatabaseHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
|
||||
@ -42,7 +44,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
|
||||
public class DebugActivity extends GBActivity {
|
||||
@ -65,6 +66,7 @@ public class DebugActivity extends GBActivity {
|
||||
private Button HeartRateButton;
|
||||
private Button exportDBButton;
|
||||
private Button importDBButton;
|
||||
private Button importOldActivityDataButton;
|
||||
private Button deleteDBButton;
|
||||
|
||||
private EditText editContent;
|
||||
@ -186,6 +188,14 @@ public class DebugActivity extends GBActivity {
|
||||
}
|
||||
});
|
||||
|
||||
importOldActivityDataButton = (Button) findViewById(R.id.mergeOldActivityData);
|
||||
importOldActivityDataButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
mergeOldActivityDbContents();
|
||||
}
|
||||
});
|
||||
|
||||
deleteDBButton = (Button) findViewById(R.id.emptyDBButton);
|
||||
deleteDBButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
@ -253,19 +263,13 @@ public class DebugActivity extends GBActivity {
|
||||
}
|
||||
|
||||
private void exportDB() {
|
||||
DBHandler dbHandler = null;
|
||||
try {
|
||||
dbHandler = GBApplication.acquireDB();
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
DBHelper helper = new DBHelper(this);
|
||||
File dir = FileUtils.getExternalFilesDir();
|
||||
File destFile = helper.exportDB(dbHandler.getHelper(), dir);
|
||||
File destFile = helper.exportDB(dbHandler, dir);
|
||||
GB.toast(this, "Exported to: " + destFile.getAbsolutePath(), Toast.LENGTH_LONG, GB.INFO);
|
||||
} catch (Exception ex) {
|
||||
GB.toast(this, "Error exporting DB: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
} finally {
|
||||
if (dbHandler != null) {
|
||||
dbHandler.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -277,22 +281,16 @@ public class DebugActivity extends GBActivity {
|
||||
.setPositiveButton("Overwrite", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
DBHandler dbHandler = null;
|
||||
try {
|
||||
dbHandler = GBApplication.acquireDB();
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
DBHelper helper = new DBHelper(DebugActivity.this);
|
||||
File dir = FileUtils.getExternalFilesDir();
|
||||
SQLiteOpenHelper sqLiteOpenHelper = dbHandler.getHelper();
|
||||
File sourceFile = new File(dir, sqLiteOpenHelper.getDatabaseName());
|
||||
helper.importDB(sqLiteOpenHelper, sourceFile);
|
||||
helper.importDB(dbHandler, sourceFile);
|
||||
helper.validateDB(sqLiteOpenHelper);
|
||||
GB.toast(DebugActivity.this, "Import successful.", Toast.LENGTH_LONG, GB.INFO);
|
||||
} catch (Exception ex) {
|
||||
GB.toast(DebugActivity.this, "Error importing DB: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
} finally {
|
||||
if (dbHandler != null) {
|
||||
dbHandler.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -304,6 +302,65 @@ public class DebugActivity extends GBActivity {
|
||||
.show();
|
||||
}
|
||||
|
||||
private void mergeOldActivityDbContents() {
|
||||
final DBHelper helper = new DBHelper(getBaseContext());
|
||||
final ActivityDatabaseHandler oldHandler = helper.getOldActivityDatabaseHandler();
|
||||
if (oldHandler == null) {
|
||||
GB.toast(this, "No old activity database found, nothing to import.", Toast.LENGTH_LONG, GB.ERROR);
|
||||
return;
|
||||
}
|
||||
selectDeviceForMergingActivityDatabaseInto(new DeviceSelectionCallback() {
|
||||
@Override
|
||||
public void invoke(final GBDevice device) {
|
||||
if (device == null) {
|
||||
GB.toast(DebugActivity.this, "No connected device to associate old activity data with.", Toast.LENGTH_LONG, GB.ERROR);
|
||||
return;
|
||||
}
|
||||
try (DBHandler targetHandler = GBApplication.acquireDB()) {
|
||||
final ProgressDialog progress = ProgressDialog.show(DebugActivity.this, "Merging Activity Data", "Please wait while merging your activity data...", true, false);
|
||||
new AsyncTask<Object,ProgressDialog,Object>() {
|
||||
@Override
|
||||
protected Object doInBackground(Object[] params) {
|
||||
helper.importOldDb(oldHandler, device, targetHandler);
|
||||
progress.dismiss();
|
||||
return null;
|
||||
}
|
||||
}.execute((Object[]) null);
|
||||
} catch (Exception ex) {
|
||||
GB.toast(DebugActivity.this, "Error importing old activity data into new database.", Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void selectDeviceForMergingActivityDatabaseInto(final DeviceSelectionCallback callback) {
|
||||
GBDevice connectedDevice = GBApplication.getDeviceManager().getSelectedDevice();
|
||||
if (connectedDevice == null) {
|
||||
callback.invoke(null);
|
||||
return;
|
||||
}
|
||||
final List<GBDevice> availableDevices = Collections.singletonList(connectedDevice);
|
||||
GBDeviceAdapter adapter = new GBDeviceAdapter(getBaseContext(), availableDevices);
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setCancelable(true)
|
||||
.setTitle("Associate old Data with Device")
|
||||
.setAdapter(adapter, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
GBDevice device = availableDevices.get(which);
|
||||
callback.invoke(device);
|
||||
}
|
||||
})
|
||||
.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
// ignore, just return
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void deleteActivityDatabase() {
|
||||
new AlertDialog.Builder(this)
|
||||
.setCancelable(true)
|
||||
@ -379,4 +436,7 @@ public class DebugActivity extends GBActivity {
|
||||
unregisterReceiver(mReceiver);
|
||||
}
|
||||
|
||||
public interface DeviceSelectionCallback {
|
||||
void invoke(GBDevice device);
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import java.util.Locale;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
@ -78,7 +79,7 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
Intent refreshIntent = new Intent(ControlCenter.ACTION_REFRESH_DEVICELIST);
|
||||
Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(refreshIntent);
|
||||
preference.setSummary(newVal.toString());
|
||||
return true;
|
||||
@ -90,7 +91,7 @@ public class SettingsActivity extends AbstractSettingsActivity {
|
||||
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
Intent refreshIntent = new Intent(ControlCenter.ACTION_REFRESH_DEVICELIST);
|
||||
Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(refreshIntent);
|
||||
preference.setSummary(newVal.toString());
|
||||
return true;
|
||||
|
@ -45,9 +45,11 @@ import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
|
||||
@ -292,9 +294,9 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
return akActivity.color;
|
||||
}
|
||||
|
||||
protected SampleProvider getProvider(GBDevice device) {
|
||||
protected SampleProvider<? extends AbstractActivitySample> getProvider(DBHandler db, GBDevice device) {
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||
return coordinator.getSampleProvider();
|
||||
return coordinator.getSampleProvider(db.getDaoSession());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -305,27 +307,27 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
* @param tsFrom
|
||||
* @param tsTo
|
||||
*/
|
||||
protected List<ActivitySample> getAllSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
SampleProvider provider = getProvider(device);
|
||||
return db.getAllActivitySamples(tsFrom, tsTo, provider);
|
||||
protected List<? extends ActivitySample> getAllSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
SampleProvider<? extends ActivitySample> provider = getProvider(db, device);
|
||||
return provider.getAllActivitySamples(tsFrom, tsTo);
|
||||
}
|
||||
|
||||
private int getTSLast24Hours(int tsTo) {
|
||||
return (tsTo) - (24 * 60 * 60); // -24 hours
|
||||
}
|
||||
|
||||
protected List<ActivitySample> getActivitySamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
SampleProvider provider = getProvider(device);
|
||||
return db.getActivitySamples(tsFrom, tsTo, provider);
|
||||
protected List<? extends AbstractActivitySample> getActivitySamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
SampleProvider<? extends AbstractActivitySample> provider = getProvider(db, device);
|
||||
return provider.getActivitySamples(tsFrom, tsTo);
|
||||
}
|
||||
|
||||
|
||||
protected List<ActivitySample> getSleepSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
SampleProvider provider = getProvider(device);
|
||||
return db.getSleepSamples(tsFrom, tsTo, provider);
|
||||
protected List<? extends ActivitySample> getSleepSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
SampleProvider<? extends ActivitySample> provider = getProvider(db, device);
|
||||
return provider.getSleepSamples(tsFrom, tsTo);
|
||||
}
|
||||
|
||||
protected List<ActivitySample> getTestSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
protected List<? extends ActivitySample> getTestSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.clear();
|
||||
cal.set(2015, Calendar.JUNE, 10, 6, 40);
|
||||
@ -333,8 +335,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
tsTo = (int) ((cal.getTimeInMillis() / 1000));
|
||||
tsFrom = tsTo - (24 * 60 * 60);
|
||||
|
||||
SampleProvider provider = getProvider(device);
|
||||
return db.getAllActivitySamples(tsFrom, tsTo, provider);
|
||||
SampleProvider<? extends ActivitySample> provider = getProvider(db, device);
|
||||
return provider.getAllActivitySamples(tsFrom, tsTo);
|
||||
}
|
||||
|
||||
protected void configureChartDefaults(Chart<?> chart) {
|
||||
@ -397,7 +399,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
*/
|
||||
protected abstract void renderCharts();
|
||||
|
||||
protected DefaultChartsData refresh(GBDevice gbDevice, List<ActivitySample> samples) {
|
||||
protected DefaultChartsData refresh(GBDevice gbDevice, List<? extends ActivitySample> samples) {
|
||||
Calendar cal = GregorianCalendar.getInstance();
|
||||
cal.clear();
|
||||
Date date;
|
||||
@ -463,13 +465,13 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
colors.add(akActivity.color);
|
||||
}
|
||||
activityEntries.add(createBarEntry(value, i));
|
||||
if (hr && isValidHeartRateValue(sample.getCustomValue())) {
|
||||
if (hr && isValidHeartRateValue(((HeartRateSample)sample).getHeartRate())) {
|
||||
if (lastHrSampleIndex > -1 && i - lastHrSampleIndex > HeartRateUtils.MAX_HR_MEASUREMENTS_GAP_MINUTES) {
|
||||
heartrateEntries.add(createLineEntry(0, lastHrSampleIndex + 1));
|
||||
heartrateEntries.add(createLineEntry(0, i - 1));
|
||||
}
|
||||
|
||||
heartrateEntries.add(createLineEntry(sample.getCustomValue(), i));
|
||||
heartrateEntries.add(createLineEntry(((HeartRateSample)sample).getHeartRate(), i));
|
||||
lastHrSampleIndex = i;
|
||||
}
|
||||
|
||||
@ -540,7 +542,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
* @param tsTo
|
||||
* @return
|
||||
*/
|
||||
protected abstract List<ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo);
|
||||
protected abstract List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo);
|
||||
|
||||
protected abstract void setupLegend(Chart chart);
|
||||
|
||||
@ -688,7 +690,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
|
||||
}
|
||||
}
|
||||
|
||||
protected List<ActivitySample> getSamples(DBHandler db, GBDevice device) {
|
||||
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device) {
|
||||
return getSamples(db, device, getTSStart(), getTSEnd());
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
|
||||
public class ActivityAnalysis {
|
||||
public ActivityAmounts calculateActivityAmounts(List<ActivitySample> samples) {
|
||||
public ActivityAmounts calculateActivityAmounts(List<? extends ActivitySample> samples) {
|
||||
ActivityAmount deepSleep = new ActivityAmount(ActivityKind.TYPE_DEEP_SLEEP);
|
||||
ActivityAmount lightSleep = new ActivityAmount(ActivityKind.TYPE_LIGHT_SLEEP);
|
||||
ActivityAmount notWorn = new ActivityAmount(ActivityKind.TYPE_NOT_WORN);
|
||||
@ -66,7 +66,7 @@ public class ActivityAnalysis {
|
||||
return result;
|
||||
}
|
||||
|
||||
public int calculateTotalSteps(List<ActivitySample> samples) {
|
||||
public int calculateTotalSteps(List<? extends ActivitySample> samples) {
|
||||
int totalSteps = 0;
|
||||
for (ActivitySample sample : samples) {
|
||||
totalSteps += sample.getSteps();
|
||||
|
@ -108,7 +108,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
|
||||
|
||||
@Override
|
||||
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
|
||||
List<ActivitySample> samples = getSamples(db, device);
|
||||
List<? extends ActivitySample> samples = getSamples(db, device);
|
||||
return refresh(device, samples);
|
||||
}
|
||||
|
||||
@ -142,7 +142,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
return getAllSamples(db, device, tsFrom, tsTo);
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
|
||||
@Override
|
||||
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
|
||||
List<ActivitySample> samples = getSamples(db, device);
|
||||
List<? extends ActivitySample> samples = getSamples(db, device);
|
||||
|
||||
MySleepChartsData mySleepChartsData = refreshSleepAmounts(device, samples);
|
||||
DefaultChartsData chartsData = refresh(device, samples);
|
||||
@ -58,7 +58,7 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
return new MyChartsData(mySleepChartsData, chartsData);
|
||||
}
|
||||
|
||||
private MySleepChartsData refreshSleepAmounts(GBDevice mGBDevice, List<ActivitySample> samples) {
|
||||
private MySleepChartsData refreshSleepAmounts(GBDevice mGBDevice, List<? extends ActivitySample> samples) {
|
||||
ActivityAnalysis analysis = new ActivityAnalysis();
|
||||
ActivityAmounts amounts = analysis.calculateActivityAmounts(samples);
|
||||
PieData data = new PieData();
|
||||
@ -195,7 +195,7 @@ public class SleepChartFragment extends AbstractChartFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
// temporary fix for totally wrong sleep amounts
|
||||
// return super.getSleepSamples(db, device, tsFrom, tsTo);
|
||||
return super.getAllSamples(db, device, tsFrom, tsTo);
|
||||
|
@ -214,7 +214,7 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
|
||||
// chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
|
||||
}
|
||||
|
||||
private List<ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, GBDevice device) {
|
||||
private List<? extends ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, GBDevice device) {
|
||||
int startTs;
|
||||
int endTs;
|
||||
|
||||
@ -233,7 +233,7 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||
return super.getAllSamples(db, device, tsFrom, tsTo);
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,11 @@ import java.util.ArrayList;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.schema.ActivityDBCreationScript;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.schema.SchemaMigration;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
@ -30,11 +34,13 @@ import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TIME
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TYPE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_GBACTIVITYSAMPLES;
|
||||
|
||||
// TODO: can be removed entirely
|
||||
public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandler {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ActivityDatabaseHandler.class);
|
||||
|
||||
private static final int DATABASE_VERSION = 7;
|
||||
private static final String UPDATER_CLASS_NAME_PREFIX = "ActivityDBUpdate_";
|
||||
|
||||
public ActivityDatabaseHandler(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
@ -52,49 +58,17 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
LOG.info("ActivityDatabase: schema upgrade requested from " + oldVersion + " to " + newVersion);
|
||||
try {
|
||||
for (int i = oldVersion + 1; i <= newVersion; i++) {
|
||||
DBUpdateScript updater = getUpdateScript(db, i);
|
||||
if (updater != null) {
|
||||
LOG.info("upgrading activity database to version " + i);
|
||||
updater.upgradeSchema(db);
|
||||
}
|
||||
}
|
||||
LOG.info("activity database is now at version " + newVersion);
|
||||
} catch (RuntimeException ex) {
|
||||
GB.toast("Error upgrading database.", Toast.LENGTH_SHORT, GB.ERROR, ex);
|
||||
throw ex; // reject upgrade
|
||||
}
|
||||
new SchemaMigration(UPDATER_CLASS_NAME_PREFIX).onUpgrade(db, oldVersion, newVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
LOG.info("ActivityDatabase: schema downgrade requested from " + oldVersion + " to " + newVersion);
|
||||
try {
|
||||
for (int i = oldVersion; i >= newVersion; i--) {
|
||||
DBUpdateScript updater = getUpdateScript(db, i);
|
||||
if (updater != null) {
|
||||
LOG.info("downgrading activity database to version " + (i - 1));
|
||||
updater.downgradeSchema(db);
|
||||
}
|
||||
}
|
||||
LOG.info("activity database is now at version " + newVersion);
|
||||
} catch (RuntimeException ex) {
|
||||
GB.toast("Error downgrading database.", Toast.LENGTH_SHORT, GB.ERROR, ex);
|
||||
throw ex; // reject downgrade
|
||||
}
|
||||
new SchemaMigration(UPDATER_CLASS_NAME_PREFIX).onDowngrade(db, oldVersion, newVersion);
|
||||
}
|
||||
|
||||
private DBUpdateScript getUpdateScript(SQLiteDatabase db, int version) {
|
||||
try {
|
||||
Class<?> updateClass = getClass().getClassLoader().loadClass(getClass().getPackage().getName() + ".schema.ActivityDBUpdate_" + version);
|
||||
return (DBUpdateScript) updateClass.newInstance();
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new RuntimeException("Error instantiating DBUpdate class for version " + version, e);
|
||||
}
|
||||
@Override
|
||||
public SQLiteDatabase getDatabase() {
|
||||
return super.getWritableDatabase();
|
||||
}
|
||||
|
||||
public void addGBActivitySample(ActivitySample sample) {
|
||||
@ -104,7 +78,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
values.put(KEY_PROVIDER, sample.getProvider().getID());
|
||||
values.put(KEY_INTENSITY, sample.getRawIntensity());
|
||||
values.put(KEY_STEPS, sample.getSteps());
|
||||
values.put(KEY_CUSTOM_SHORT, sample.getCustomValue());
|
||||
// values.put(KEY_CUSTOM_SHORT, sample.getCustomValue());
|
||||
values.put(KEY_TYPE, sample.getRawKind());
|
||||
|
||||
db.insert(TABLE_GBACTIVITYSAMPLES, null, values);
|
||||
@ -121,8 +95,13 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
* @param kind the raw activity kind of the sample
|
||||
* @param customShortValue
|
||||
*/
|
||||
@Override
|
||||
public void addGBActivitySample(int timestamp, int provider, int intensity, int steps, int kind, int customShortValue) {
|
||||
public void addGBActivitySample(AbstractActivitySample sample) {
|
||||
float intensity = sample.getIntensity();
|
||||
int steps = sample.getSteps();
|
||||
int kind = sample.getRawKind();
|
||||
int timestamp = sample.getTimestamp();
|
||||
int customShortValue = 0;
|
||||
|
||||
if (intensity < 0) {
|
||||
LOG.error("negative intensity received, ignoring");
|
||||
intensity = 0;
|
||||
@ -140,7 +119,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
try (SQLiteDatabase db = this.getWritableDatabase()) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(KEY_TIMESTAMP, timestamp);
|
||||
values.put(KEY_PROVIDER, provider);
|
||||
// values.put(KEY_PROVIDER, provider);
|
||||
values.put(KEY_INTENSITY, intensity);
|
||||
values.put(KEY_STEPS, steps);
|
||||
values.put(KEY_TYPE, kind);
|
||||
@ -150,8 +129,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGBActivitySamples(ActivitySample[] activitySamples) {
|
||||
public void addGBActivitySamples(AbstractActivitySample[] activitySamples) {
|
||||
try (SQLiteDatabase db = this.getWritableDatabase()) {
|
||||
|
||||
String sql = "INSERT INTO " + TABLE_GBACTIVITYSAMPLES + " (" + KEY_TIMESTAMP + "," +
|
||||
@ -167,7 +145,7 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
statement.bindLong(3, activitySample.getRawIntensity());
|
||||
statement.bindLong(4, activitySample.getSteps());
|
||||
statement.bindLong(5, activitySample.getRawKind());
|
||||
statement.bindLong(6, activitySample.getCustomValue());
|
||||
// statement.bindLong(6, activitySample.getCustomValue());
|
||||
statement.execute();
|
||||
}
|
||||
db.setTransactionSuccessful();
|
||||
@ -183,20 +161,27 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ACTIVITY, provider);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeDb() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void openDb() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQLiteOpenHelper getHelper() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
GBApplication.releaseDB();
|
||||
}
|
||||
|
||||
public ArrayList<ActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to, SampleProvider provider) {
|
||||
return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL, provider);
|
||||
}
|
||||
|
||||
public ArrayList<ActivitySample> getAllActivitySamples() {
|
||||
return getActivitySamples(null, "timestamp", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all available activity samples from between the two timestamps (inclusive), of the given
|
||||
* provided and type(s).
|
||||
@ -214,10 +199,17 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
if (timestamp_from < 0) {
|
||||
throw new IllegalArgumentException("negative timestamp_from");
|
||||
}
|
||||
ArrayList<ActivitySample> samples = new ArrayList<>();
|
||||
final String where = "(provider=" + provider.getID() + " and timestamp>=" + timestamp_from + " and timestamp<=" + timestamp_to + getWhereClauseFor(activityTypes, provider) + ")";
|
||||
LOG.info("Activity query where: " + where);
|
||||
final String order = "timestamp";
|
||||
|
||||
ArrayList<ActivitySample> samples = getActivitySamples(where, order, null);
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
private ArrayList<ActivitySample> getActivitySamples(String where, String order, SampleProvider provider) {
|
||||
ArrayList<ActivitySample> samples = new ArrayList<>();
|
||||
try (SQLiteDatabase db = this.getReadableDatabase()) {
|
||||
try (Cursor cursor = db.query(TABLE_GBACTIVITYSAMPLES, null, where, null, null, null, order)) {
|
||||
LOG.info("Activity query result: " + cursor.getCount() + " samples");
|
||||
@ -238,7 +230,6 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return samples;
|
||||
}
|
||||
|
||||
@ -259,7 +250,6 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeStoredSamplesType(int timestampFrom, int timestampTo, int kind, SampleProvider provider) {
|
||||
try (SQLiteDatabase db = this.getReadableDatabase()) {
|
||||
String sql = "UPDATE " + TABLE_GBACTIVITYSAMPLES + " SET " + KEY_TYPE + "= ? WHERE "
|
||||
@ -275,7 +265,6 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeStoredSamplesType(int timestampFrom, int timestampTo, int fromKind, int toKind, SampleProvider provider) {
|
||||
try (SQLiteDatabase db = this.getReadableDatabase()) {
|
||||
String sql = "UPDATE " + TABLE_GBACTIVITYSAMPLES + " SET " + KEY_TYPE + "= ? WHERE "
|
||||
@ -293,7 +282,6 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int fetchLatestTimestamp(SampleProvider provider) {
|
||||
try (SQLiteDatabase db = this.getReadableDatabase()) {
|
||||
try (Cursor cursor = db.query(TABLE_GBACTIVITYSAMPLES, new String[]{KEY_TIMESTAMP}, KEY_PROVIDER + "=" + String.valueOf(provider.getID()), null, null, null, KEY_TIMESTAMP + " DESC", "1")) {
|
||||
@ -304,4 +292,28 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public boolean hasContent() {
|
||||
try {
|
||||
try (SQLiteDatabase db = this.getReadableDatabase()) {
|
||||
try (Cursor cursor = db.query(TABLE_GBACTIVITYSAMPLES, new String[]{KEY_TIMESTAMP}, null, null, null, null, null, "1")) {
|
||||
return cursor.moveToFirst();
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
// can't expect anything
|
||||
GB.log("Error looking for old activity data: " + ex.getMessage(), GB.ERROR, ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DaoSession getDaoSession() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DaoMaster getDaoMaster() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
@ -26,16 +26,10 @@ public abstract class DBAccess extends AsyncTask {
|
||||
|
||||
@Override
|
||||
protected Object doInBackground(Object[] params) {
|
||||
DBHandler handler = null;
|
||||
try {
|
||||
handler = GBApplication.acquireDB();
|
||||
doInBackground(handler);
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
doInBackground(db);
|
||||
} catch (Exception e) {
|
||||
mError = e;
|
||||
} finally {
|
||||
if (handler != null) {
|
||||
handler.release();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.database;
|
||||
|
||||
/**
|
||||
* TODO: Legacy, can be removed once migration support for old ActivityDatabase is removed
|
||||
*/
|
||||
public class DBConstants {
|
||||
public static final String DATABASE_NAME = "ActivityDatabase";
|
||||
|
||||
|
@ -3,37 +3,35 @@ package nodomain.freeyourgadget.gadgetbridge.database;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
|
||||
/**
|
||||
* Provides lowlevel access to the database.
|
||||
*/
|
||||
public interface DBHandler extends AutoCloseable {
|
||||
/**
|
||||
* Closes the database.
|
||||
*/
|
||||
void closeDb();
|
||||
|
||||
/**
|
||||
* Opens the database. Note that this is only possible after an explicit
|
||||
* #closeDb(). Initially the db is implicitly open.
|
||||
*/
|
||||
void openDb();
|
||||
|
||||
public interface DBHandler {
|
||||
SQLiteOpenHelper getHelper();
|
||||
|
||||
/**
|
||||
* Releases the DB handler. No access may be performed after calling this method.
|
||||
* Same as calling {@link GBApplication#releaseDB()}
|
||||
* Releases the DB handler. No DB access will be possible before
|
||||
* #openDb() will be called.
|
||||
*/
|
||||
void release();
|
||||
void close() throws Exception;
|
||||
|
||||
List<ActivitySample> getAllActivitySamples(int tsFrom, int tsTo, SampleProvider provider);
|
||||
|
||||
List<ActivitySample> getActivitySamples(int tsFrom, int tsTo, SampleProvider provider);
|
||||
|
||||
List<ActivitySample> getSleepSamples(int tsFrom, int tsTo, SampleProvider provider);
|
||||
|
||||
void addGBActivitySample(int timestamp, int provider, int intensity, int steps, int kind, int heartrate);
|
||||
|
||||
void addGBActivitySamples(ActivitySample[] activitySamples);
|
||||
|
||||
SQLiteDatabase getWritableDatabase();
|
||||
|
||||
void changeStoredSamplesType(int timestampFrom, int timestampTo, int kind, SampleProvider provider);
|
||||
|
||||
void changeStoredSamplesType(int timestampFrom, int timestampTo, int fromKind, int toKind, SampleProvider provider);
|
||||
|
||||
int fetchLatestTimestamp(SampleProvider provider);
|
||||
SQLiteDatabase getDatabase();
|
||||
|
||||
DaoMaster getDaoMaster();
|
||||
DaoSession getDaoSession();
|
||||
}
|
||||
|
@ -4,16 +4,52 @@ import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import de.greenrobot.dao.query.Query;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributesDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.UserAttributes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.UserDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ValidByDate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_CUSTOM_SHORT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_INTENSITY;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_STEPS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TIMESTAMP;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TYPE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_GBACTIVITYSAMPLES;
|
||||
|
||||
/**
|
||||
* Provides utiliy access to some common entities, so you won't need to use
|
||||
* their DAO classes.
|
||||
*
|
||||
* Maybe this code should actually be in the DAO classes themselves, but then
|
||||
* these should be under revision control instead of 100% generated at build time.
|
||||
*/
|
||||
public class DBHelper {
|
||||
private final Context context;
|
||||
|
||||
@ -21,41 +57,57 @@ public class DBHelper {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private String getClosedDBPath(SQLiteOpenHelper dbHandler) throws IllegalStateException {
|
||||
SQLiteDatabase db = dbHandler.getReadableDatabase();
|
||||
/**
|
||||
* Closes the database and returns its name.
|
||||
* Important: after calling this, you have to DBHandler#openDb() it again
|
||||
* to get it back to work.
|
||||
* @param dbHandler
|
||||
* @return
|
||||
* @throws IllegalStateException
|
||||
*/
|
||||
private String getClosedDBPath(DBHandler dbHandler) throws IllegalStateException {
|
||||
SQLiteDatabase db = dbHandler.getDatabase();
|
||||
String path = db.getPath();
|
||||
db.close();
|
||||
dbHandler.closeDb();
|
||||
if (db.isOpen()) { // reference counted, so may still be open
|
||||
throw new IllegalStateException("Database must be closed");
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
public File exportDB(SQLiteOpenHelper dbHandler, File toDir) throws IllegalStateException, IOException {
|
||||
public File exportDB(DBHandler dbHandler, File toDir) throws IllegalStateException, IOException {
|
||||
String dbPath = getClosedDBPath(dbHandler);
|
||||
File sourceFile = new File(dbPath);
|
||||
File destFile = new File(toDir, sourceFile.getName());
|
||||
if (destFile.exists()) {
|
||||
File backup = new File(toDir, destFile.getName() + "_" + getDate());
|
||||
destFile.renameTo(backup);
|
||||
} else if (!toDir.exists()) {
|
||||
if (!toDir.mkdirs()) {
|
||||
throw new IOException("Unable to create directory: " + toDir.getAbsolutePath());
|
||||
try {
|
||||
File sourceFile = new File(dbPath);
|
||||
File destFile = new File(toDir, sourceFile.getName());
|
||||
if (destFile.exists()) {
|
||||
File backup = new File(toDir, destFile.getName() + "_" + getDate());
|
||||
destFile.renameTo(backup);
|
||||
} else if (!toDir.exists()) {
|
||||
if (!toDir.mkdirs()) {
|
||||
throw new IOException("Unable to create directory: " + toDir.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileUtils.copyFile(sourceFile, destFile);
|
||||
return destFile;
|
||||
FileUtils.copyFile(sourceFile, destFile);
|
||||
return destFile;
|
||||
} finally {
|
||||
dbHandler.openDb();
|
||||
}
|
||||
}
|
||||
|
||||
private String getDate() {
|
||||
return new SimpleDateFormat("yyyyMMdd-HHmmss", Locale.US).format(new Date());
|
||||
}
|
||||
|
||||
public void importDB(SQLiteOpenHelper dbHandler, File fromFile) throws IllegalStateException, IOException {
|
||||
public void importDB(DBHandler dbHandler, File fromFile) throws IllegalStateException, IOException {
|
||||
String dbPath = getClosedDBPath(dbHandler);
|
||||
File toFile = new File(dbPath);
|
||||
FileUtils.copyFile(fromFile, toFile);
|
||||
try {
|
||||
File toFile = new File(dbPath);
|
||||
FileUtils.copyFile(fromFile, toFile);
|
||||
} finally {
|
||||
dbHandler.openDb();
|
||||
}
|
||||
}
|
||||
|
||||
public void validateDB(SQLiteOpenHelper dbHandler) throws IOException {
|
||||
@ -99,4 +151,239 @@ public class DBHelper {
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static User getUser(DaoSession session) {
|
||||
ActivityUser prefsUser = new ActivityUser();
|
||||
UserDao userDao = session.getUserDao();
|
||||
Query<User> query = userDao.queryBuilder().where(UserDao.Properties.Name.eq(prefsUser.getName())).build();
|
||||
List<User> users = query.list();
|
||||
User user;
|
||||
if (users.isEmpty()) {
|
||||
user = createUser(prefsUser, session);
|
||||
} else {
|
||||
user = users.get(0); // TODO: multiple users support?
|
||||
}
|
||||
ensureUserAttributes(user, prefsUser, session);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
private static User createUser(ActivityUser prefsUser, DaoSession session) {
|
||||
User user = new User();
|
||||
user.setName(prefsUser.getName());
|
||||
user.setBirthday(prefsUser.getUserBirthday());
|
||||
user.setGender(prefsUser.getGender());
|
||||
session.getUserDao().insert(user);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
private static void ensureUserAttributes(User user, ActivityUser prefsUser, DaoSession session) {
|
||||
List<UserAttributes> userAttributes = user.getUserAttributesList();
|
||||
if (hasUpToDateUserAttributes(userAttributes, prefsUser)) {
|
||||
return;
|
||||
}
|
||||
UserAttributes attributes = new UserAttributes();
|
||||
attributes.setValidFromUTC(DateTimeUtils.todayUTC());
|
||||
attributes.setHeightCM(prefsUser.getHeightCm());
|
||||
attributes.setWeightKG(prefsUser.getWeightKg());
|
||||
attributes.setUserId(user.getId());
|
||||
session.getUserAttributesDao().insert(attributes);
|
||||
|
||||
userAttributes.add(attributes);
|
||||
}
|
||||
|
||||
private static boolean hasUpToDateUserAttributes(List<UserAttributes> userAttributes, ActivityUser prefsUser) {
|
||||
for (UserAttributes attr : userAttributes) {
|
||||
if (!isValidNow(attr)) {
|
||||
return false;
|
||||
}
|
||||
if (isEqual(attr, prefsUser)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: move this into db queries?
|
||||
private static boolean isValidNow(ValidByDate element) {
|
||||
Calendar cal = DateTimeUtils.getCalendarUTC();
|
||||
Date nowUTC = cal.getTime();
|
||||
return isValid(element, nowUTC);
|
||||
}
|
||||
|
||||
private static boolean isValid(ValidByDate element, Date nowUTC) {
|
||||
Date validFromUTC = element.getValidFromUTC();
|
||||
Date validToUTC = element.getValidToUTC();
|
||||
if (nowUTC.before(validFromUTC)) {
|
||||
return false;
|
||||
}
|
||||
if (validToUTC != null && nowUTC.after(validToUTC)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isEqual(UserAttributes attr, ActivityUser prefsUser) {
|
||||
if (prefsUser.getHeightCm() != attr.getHeightCM()) {
|
||||
return false;
|
||||
}
|
||||
if (prefsUser.getWeightKg() != attr.getWeightKG()) {
|
||||
return false;
|
||||
}
|
||||
if (!Integer.valueOf(prefsUser.getSleepDuration()).equals(attr.getSleepGoalHPD())) {
|
||||
return false;
|
||||
}
|
||||
if (!Integer.valueOf(prefsUser.getStepsGoal()).equals(attr.getStepsGoalSPD())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isEqual(DeviceAttributes attr, GBDevice gbDevice) {
|
||||
if (!isEqual(attr.getFirmwareVersion1(), gbDevice.getFirmwareVersion())) {
|
||||
return false;
|
||||
}
|
||||
if (!isEqual(attr.getFirmwareVersion2(), gbDevice.getFirmwareVersion2())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isEqual(String s1, String s2) {
|
||||
if (s1 == s2) {
|
||||
return true;
|
||||
}
|
||||
if (s1 != null) {
|
||||
return s1.equals(s2);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Device getDevice(GBDevice gbDevice, DaoSession session) {
|
||||
DeviceDao deviceDao = session.getDeviceDao();
|
||||
Query<Device> query = deviceDao.queryBuilder().where(DeviceDao.Properties.Identifier.eq(gbDevice.getAddress())).build();
|
||||
List<Device> devices = query.list();
|
||||
Device device;
|
||||
if (devices.isEmpty()) {
|
||||
device = createDevice(session, gbDevice);
|
||||
} else {
|
||||
device = devices.get(0);
|
||||
}
|
||||
ensureDeviceAttributes(device, gbDevice, session);
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
private static Device createDevice(DaoSession session, GBDevice gbDevice) {
|
||||
Device device = new Device();
|
||||
device.setIdentifier(gbDevice.getAddress());
|
||||
device.setName(gbDevice.getName());
|
||||
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice);
|
||||
device.setManufacturer(coordinator.getManufacturer());
|
||||
session.getDeviceDao().insert(device);
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
private static void ensureDeviceAttributes(Device device, GBDevice gbDevice, DaoSession session) {
|
||||
List<DeviceAttributes> deviceAttributes = device.getDeviceAttributesList();
|
||||
if (hasUpToDateDeviceAttributes(deviceAttributes, gbDevice)) {
|
||||
return;
|
||||
}
|
||||
DeviceAttributes attributes = new DeviceAttributes();
|
||||
|
||||
attributes.setDeviceId(device.getId());
|
||||
attributes.setValidFromUTC(DateTimeUtils.todayUTC());
|
||||
attributes.setFirmwareVersion1(gbDevice.getFirmwareVersion());
|
||||
attributes.setFirmwareVersion2(gbDevice.getFirmwareVersion2());
|
||||
DeviceAttributesDao attributesDao = session.getDeviceAttributesDao();
|
||||
attributesDao.insert(attributes);
|
||||
|
||||
deviceAttributes.add(attributes);
|
||||
}
|
||||
|
||||
private static boolean hasUpToDateDeviceAttributes(List<DeviceAttributes> deviceAttributes, GBDevice gbDevice) {
|
||||
for (DeviceAttributes attr : deviceAttributes) {
|
||||
if (!isValidNow(attr)) {
|
||||
return false;
|
||||
}
|
||||
if (isEqual(attr, gbDevice)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the old activity database handler if there is any content in that
|
||||
* db, or null otherwise.
|
||||
* @return the old activity db handler or null
|
||||
*/
|
||||
@Nullable
|
||||
public ActivityDatabaseHandler getOldActivityDatabaseHandler() {
|
||||
ActivityDatabaseHandler handler = new ActivityDatabaseHandler(context);
|
||||
if (handler.hasContent()) {
|
||||
return handler;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void importOldDb(ActivityDatabaseHandler oldDb, GBDevice targetDevice, DBHandler targetDBHandler) {
|
||||
DaoSession tempSession = targetDBHandler.getDaoMaster().newSession();
|
||||
try {
|
||||
importActivityDatabase(oldDb, targetDevice, tempSession);
|
||||
} finally {
|
||||
tempSession.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEmpty(DaoSession session) {
|
||||
long totalSamplesCount = session.getMiBandActivitySampleDao().count();
|
||||
totalSamplesCount += session.getPebbleActivitySampleDao().count();
|
||||
return totalSamplesCount == 0;
|
||||
}
|
||||
|
||||
private void importActivityDatabase(ActivityDatabaseHandler oldDbHandler, GBDevice targetDevice, DaoSession session) {
|
||||
try (SQLiteDatabase oldDB = oldDbHandler.getReadableDatabase()) {
|
||||
User user = DBHelper.getUser(session);
|
||||
for (DeviceCoordinator coordinator : DeviceHelper.getInstance().getAllCoordinators()) {
|
||||
AbstractSampleProvider<? extends AbstractActivitySample> sampleProvider = (AbstractSampleProvider<? extends AbstractActivitySample>) coordinator.getSampleProvider(session);
|
||||
importActivitySamples(oldDB, targetDevice, session, sampleProvider, user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends AbstractActivitySample> void importActivitySamples(SQLiteDatabase fromDb, GBDevice targetDevice, DaoSession targetSession, AbstractSampleProvider<T> sampleProvider, User user) {
|
||||
String order = "timestamp";
|
||||
final String where = "provider=" + sampleProvider.getID();
|
||||
|
||||
try (Cursor cursor = fromDb.query(TABLE_GBACTIVITYSAMPLES, null, where, null, null, null, order)) {
|
||||
int colTimeStamp = cursor.getColumnIndex(KEY_TIMESTAMP);
|
||||
int colIntensity = cursor.getColumnIndex(KEY_INTENSITY);
|
||||
int colSteps = cursor.getColumnIndex(KEY_STEPS);
|
||||
int colType = cursor.getColumnIndex(KEY_TYPE);
|
||||
int colCustomShort = cursor.getColumnIndex(KEY_CUSTOM_SHORT);
|
||||
Long deviceId = DBHelper.getDevice(targetDevice, targetSession).getId();
|
||||
Long userId = user.getId();
|
||||
List<T> newSamples = new ArrayList<>(cursor.getCount());
|
||||
while (cursor.moveToNext()) {
|
||||
T newSample = sampleProvider.createActivitySample();
|
||||
newSample.setUserId(userId);
|
||||
newSample.setDeviceId(deviceId);
|
||||
newSample.setTimestamp(cursor.getInt(colTimeStamp));
|
||||
newSample.setRawKind(cursor.getInt(colType));
|
||||
newSample.setProvider(sampleProvider);
|
||||
newSample.setRawIntensity(cursor.getInt(colIntensity));
|
||||
newSample.setSteps(cursor.getInt(colSteps));
|
||||
|
||||
int hrValue = cursor.getInt(colCustomShort);
|
||||
if (newSample instanceof HeartRateSample) {
|
||||
((HeartRateSample)newSample).setHeartRate(hrValue);
|
||||
}
|
||||
newSamples.add(newSample);
|
||||
}
|
||||
sampleProvider.getSampleDao().insertOrReplaceInTx(newSamples, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,52 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.database;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.schema.SchemaMigration;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.HeartRateSample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_CUSTOM_SHORT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_INTENSITY;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_STEPS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TIMESTAMP;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TYPE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_GBACTIVITYSAMPLES;
|
||||
|
||||
public class DBOpenHelper extends DaoMaster.OpenHelper {
|
||||
private final String updaterClassNamePrefix;
|
||||
private final Context context;
|
||||
|
||||
public DBOpenHelper(Context context, String dbName, SQLiteDatabase.CursorFactory factory) {
|
||||
super(context, dbName, factory);
|
||||
updaterClassNamePrefix = dbName + "Update_";
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
new SchemaMigration(updaterClassNamePrefix).onUpgrade(db, oldVersion, newVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
new SchemaMigration(updaterClassNamePrefix).onDowngrade(db, oldVersion, newVersion);
|
||||
}
|
||||
|
||||
public Context getContext() {
|
||||
return context;
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.database.schema;
|
||||
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class SchemaMigration {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SchemaMigration.class);
|
||||
private final String classNamePrefix;
|
||||
|
||||
public SchemaMigration(String updaterClassNamePrefix) {
|
||||
classNamePrefix = updaterClassNamePrefix;
|
||||
}
|
||||
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
LOG.info("ActivityDatabase: schema upgrade requested from " + oldVersion + " to " + newVersion);
|
||||
try {
|
||||
for (int i = oldVersion + 1; i <= newVersion; i++) {
|
||||
DBUpdateScript updater = getUpdateScript(db, i);
|
||||
if (updater != null) {
|
||||
LOG.info("upgrading activity database to version " + i);
|
||||
updater.upgradeSchema(db);
|
||||
}
|
||||
}
|
||||
LOG.info("activity database is now at version " + newVersion);
|
||||
} catch (RuntimeException ex) {
|
||||
GB.toast("Error upgrading database.", Toast.LENGTH_SHORT, GB.ERROR, ex);
|
||||
throw ex; // reject upgrade
|
||||
}
|
||||
}
|
||||
|
||||
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
LOG.info("ActivityDatabase: schema downgrade requested from " + oldVersion + " to " + newVersion);
|
||||
try {
|
||||
for (int i = oldVersion; i >= newVersion; i--) {
|
||||
DBUpdateScript updater = getUpdateScript(db, i);
|
||||
if (updater != null) {
|
||||
LOG.info("downgrading activity database to version " + (i - 1));
|
||||
updater.downgradeSchema(db);
|
||||
}
|
||||
}
|
||||
LOG.info("activity database is now at version " + newVersion);
|
||||
} catch (RuntimeException ex) {
|
||||
GB.toast("Error downgrading database.", Toast.LENGTH_SHORT, GB.ERROR, ex);
|
||||
throw ex; // reject downgrade
|
||||
}
|
||||
}
|
||||
|
||||
private DBUpdateScript getUpdateScript(SQLiteDatabase db, int version) {
|
||||
try {
|
||||
Class<?> updateClass = getClass().getClassLoader().loadClass(getClass().getPackage().getName() + ".schema." + classNamePrefix + version);
|
||||
return (DBUpdateScript) updateClass.newInstance();
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new RuntimeException("Error instantiating DBUpdate class for version " + version, e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
import de.greenrobot.dao.Property;
|
||||
import de.greenrobot.dao.query.QueryBuilder;
|
||||
import de.greenrobot.dao.query.WhereCondition;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
|
||||
/**
|
||||
* Base class for all sample providers. A Sample provider is device specific and provides
|
||||
* access to the device specific samples. There are both read and write operations.
|
||||
* @param <T> the sample type
|
||||
*/
|
||||
public abstract class AbstractSampleProvider<T extends AbstractActivitySample> implements SampleProvider<T> {
|
||||
private static final WhereCondition[] NO_CONDITIONS = new WhereCondition[0];
|
||||
private final DaoSession mSession;
|
||||
|
||||
protected AbstractSampleProvider(DaoSession session) {
|
||||
mSession = session;
|
||||
}
|
||||
|
||||
public DaoSession getSession() {
|
||||
return mSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> getAllActivitySamples(int timestamp_from, int timestamp_to) {
|
||||
return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> getActivitySamples(int timestamp_from, int timestamp_to) {
|
||||
return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ACTIVITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> getSleepSamples(int timestamp_from, int timestamp_to) {
|
||||
return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_SLEEP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int fetchLatestTimestamp() {
|
||||
QueryBuilder<T> qb = getSampleDao().queryBuilder();
|
||||
qb.orderDesc(getTimestampSampleProperty());
|
||||
qb.limit(1);
|
||||
List<T> list = qb.build().list();
|
||||
if (list.size() >= 1) {
|
||||
return list.get(0).getTimestamp();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGBActivitySample(T activitySample) {
|
||||
getSampleDao().insertOrReplace(activitySample);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGBActivitySamples(T[] activitySamples) {
|
||||
getSampleDao().insertOrReplaceInTx(activitySamples);
|
||||
}
|
||||
|
||||
public void changeStoredSamplesType(int timestampFrom, int timestampTo, int kind) {
|
||||
List<T> samples = getAllActivitySamples(timestampFrom, timestampTo);
|
||||
for (T sample : samples) {
|
||||
sample.setRawKind(kind);
|
||||
}
|
||||
getSampleDao().updateInTx(samples);
|
||||
}
|
||||
|
||||
public void changeStoredSamplesType(int timestampFrom, int timestampTo, int fromKind, int toKind) {
|
||||
List<T> samples = getGBActivitySamples(timestampFrom, timestampTo, fromKind);
|
||||
for (T sample : samples) {
|
||||
sample.setRawKind(toKind);
|
||||
}
|
||||
getSampleDao().updateInTx(samples);
|
||||
}
|
||||
|
||||
protected List<T> getGBActivitySamples(int timestamp_from, int timestamp_to, int activityType) {
|
||||
QueryBuilder<T> qb = getSampleDao().queryBuilder();
|
||||
Property timestampProperty = getTimestampSampleProperty();
|
||||
qb.where(timestampProperty.ge(timestamp_from))
|
||||
.where(timestampProperty.le(timestamp_to), getClauseForActivityType(qb, activityType));
|
||||
List<T> samples = qb.build().list();
|
||||
for (T sample : samples) {
|
||||
sample.setProvider(this);
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
private WhereCondition[] getClauseForActivityType(QueryBuilder qb, int activityTypes) {
|
||||
if (activityTypes == ActivityKind.TYPE_ALL) {
|
||||
return NO_CONDITIONS;
|
||||
}
|
||||
|
||||
int[] dbActivityTypes = ActivityKind.mapToDBActivityTypes(activityTypes, this);
|
||||
WhereCondition activityTypeCondition = getActivityTypeConditions(qb, dbActivityTypes);
|
||||
return new WhereCondition[] { activityTypeCondition };
|
||||
}
|
||||
|
||||
private WhereCondition getActivityTypeConditions(QueryBuilder qb, int[] dbActivityTypes) {
|
||||
// What a crappy QueryBuilder API ;-( QueryBuilder.or(WhereCondition[]) with a runtime array length
|
||||
// check would have worked just fine.
|
||||
if (dbActivityTypes.length == 0) {
|
||||
return null;
|
||||
}
|
||||
Property rawKindProperty = getRawKindSampleProperty();
|
||||
if (dbActivityTypes.length == 1) {
|
||||
return rawKindProperty.eq(dbActivityTypes[0]);
|
||||
}
|
||||
if (dbActivityTypes.length == 2) {
|
||||
return qb.or(rawKindProperty.eq(dbActivityTypes[0]),
|
||||
rawKindProperty.eq(dbActivityTypes[1]));
|
||||
}
|
||||
final int offset = 2;
|
||||
int len = dbActivityTypes.length - offset;
|
||||
WhereCondition[] trailingConditions = new WhereCondition[len];
|
||||
for (int i = 0; i < len; i++) {
|
||||
trailingConditions[i] = rawKindProperty.eq(dbActivityTypes[i + offset]);
|
||||
}
|
||||
return qb.or(rawKindProperty.eq(dbActivityTypes[0]),
|
||||
rawKindProperty.eq(dbActivityTypes[1]),
|
||||
trailingConditions);
|
||||
}
|
||||
|
||||
public abstract AbstractDao<T,?> getSampleDao();
|
||||
|
||||
protected abstract Property getRawKindSampleProperty();
|
||||
protected abstract Property getTimestampSampleProperty();
|
||||
}
|
@ -4,8 +4,12 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
/**
|
||||
@ -82,7 +86,7 @@ public interface DeviceCoordinator {
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
SampleProvider getSampleProvider();
|
||||
SampleProvider<? extends ActivitySample> getSampleProvider(DaoSession session);
|
||||
|
||||
/**
|
||||
* Finds an install handler for the given uri that can install the given
|
||||
@ -109,4 +113,9 @@ public interface DeviceCoordinator {
|
||||
boolean supportsAlarmConfiguration();
|
||||
|
||||
int getTapString();
|
||||
|
||||
/**
|
||||
* Returns the readable name of the manufacturer.
|
||||
*/
|
||||
String getManufacturer();
|
||||
}
|
||||
|
@ -0,0 +1,128 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
|
||||
/**
|
||||
* Provides access to the list of devices managed by Gadgetbridge.
|
||||
* Changes to the devices (e.g. connection state) or the list of devices
|
||||
* are broadcasted via #ACTION_DEVICE_CHANGED
|
||||
*/
|
||||
public class DeviceManager {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DeviceManager.class);
|
||||
|
||||
/**
|
||||
* Intent action to notify that the list of devices has changed.
|
||||
*/
|
||||
public static final String ACTION_DEVICES_CHANGED
|
||||
= "nodomain.freeyourgadget.gadgetbridge.devices.devicemanager.action.devices_changed";
|
||||
/**
|
||||
* Intent action to notify this class that the list of devices shall be refreshed.
|
||||
*/
|
||||
public static final String ACTION_REFRESH_DEVICELIST
|
||||
= "nodomain.freeyourgadget.gadgetbridge.devices.devicemanager.action.set_version";
|
||||
private final Context context;
|
||||
/**
|
||||
* This list is final, it will never be recreated. Only its contents change.
|
||||
* This allows direct access to the list from ListAdapters.
|
||||
*/
|
||||
private final List<GBDevice> deviceList = new ArrayList<>();
|
||||
private GBDevice selectedDevice = null;
|
||||
|
||||
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
switch (action) {
|
||||
case ACTION_REFRESH_DEVICELIST:
|
||||
case BluetoothDevice.ACTION_BOND_STATE_CHANGED:
|
||||
refreshPairedDevices();
|
||||
break;
|
||||
case GBDevice.ACTION_DEVICE_CHANGED:
|
||||
GBDevice dev = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
if (dev.getAddress() != null) {
|
||||
int index = deviceList.indexOf(dev); // search by address
|
||||
if (index >= 0) {
|
||||
deviceList.set(index, dev);
|
||||
} else {
|
||||
deviceList.add(dev);
|
||||
}
|
||||
}
|
||||
updateSelectedDevice(dev);
|
||||
refreshPairedDevices();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public DeviceManager(Context context) {
|
||||
this.context = context;
|
||||
IntentFilter filterLocal = new IntentFilter();
|
||||
filterLocal.addAction(DeviceManager.ACTION_REFRESH_DEVICELIST);
|
||||
filterLocal.addAction(GBDevice.ACTION_DEVICE_CHANGED);
|
||||
filterLocal.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
|
||||
LocalBroadcastManager.getInstance(context).registerReceiver(mReceiver, filterLocal);
|
||||
context.registerReceiver(mReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
|
||||
|
||||
refreshPairedDevices();
|
||||
}
|
||||
|
||||
private void updateSelectedDevice(GBDevice dev) {
|
||||
if (selectedDevice == null) {
|
||||
selectedDevice = dev;
|
||||
} else {
|
||||
if (!selectedDevice.equals(dev)) {
|
||||
if (selectedDevice.isConnected() && dev.isConnected()) {
|
||||
LOG.warn("multiple connected devices -- this is currently not really supported");
|
||||
selectedDevice = dev; // use the last one that changed
|
||||
}
|
||||
if (!selectedDevice.isConnected()) {
|
||||
selectedDevice = dev; // use the last one that changed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshPairedDevices() {
|
||||
Set<GBDevice> availableDevices = DeviceHelper.getInstance().getAvailableDevices(context);
|
||||
deviceList.retainAll(availableDevices);
|
||||
for (GBDevice availableDevice : availableDevices) {
|
||||
if (!deviceList.contains(availableDevice)) {
|
||||
deviceList.add(availableDevice);
|
||||
}
|
||||
}
|
||||
|
||||
notifyDevicesChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* The returned list is final, it will never be recreated. Only its contents change.
|
||||
* This allows direct access to the list from ListAdapters.
|
||||
*/
|
||||
public List<GBDevice> getDevices() {
|
||||
return Collections.unmodifiableList(deviceList);
|
||||
}
|
||||
|
||||
public GBDevice getSelectedDevice() {
|
||||
return selectedDevice;
|
||||
}
|
||||
|
||||
private void notifyDevicesChanged() {
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(ACTION_DEVICES_CHANGED));
|
||||
}
|
||||
}
|
@ -1,6 +1,11 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices;
|
||||
|
||||
public interface SampleProvider {
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||
|
||||
public interface SampleProvider<T extends AbstractActivitySample> {
|
||||
// TODO: these constants can all be removed
|
||||
int PROVIDER_MIBAND = 0;
|
||||
int PROVIDER_PEBBLE_MORPHEUZ = 1;
|
||||
int PROVIDER_PEBBLE_GADGETBRIDGE = 2;
|
||||
@ -8,6 +13,8 @@ public interface SampleProvider {
|
||||
int PROVIDER_PEBBLE_HEALTH = 4;
|
||||
|
||||
int PROVIDER_UNKNOWN = 100;
|
||||
// TODO: can also be removed
|
||||
int getID();
|
||||
|
||||
int normalizeType(int rawType);
|
||||
|
||||
@ -15,5 +22,21 @@ public interface SampleProvider {
|
||||
|
||||
float normalizeIntensity(int rawIntensity);
|
||||
|
||||
int getID();
|
||||
List<T> getAllActivitySamples(int timestamp_from, int timestamp_to);
|
||||
|
||||
List<T> getActivitySamples(int timestamp_from, int timestamp_to);
|
||||
|
||||
List<T> getSleepSamples(int timestamp_from, int timestamp_to);
|
||||
|
||||
void changeStoredSamplesType(int timestampFrom, int timestampTo, int kind);
|
||||
|
||||
void changeStoredSamplesType(int timestampFrom, int timestampTo, int fromKind, int toKind);
|
||||
|
||||
int fetchLatestTimestamp();
|
||||
|
||||
void addGBActivitySample(T activitySample);
|
||||
|
||||
void addGBActivitySamples(T[] activitySamples);
|
||||
|
||||
T createActivitySample();
|
||||
}
|
||||
|
@ -4,7 +4,12 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
@ -29,6 +34,47 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List getAllActivitySamples(int timestamp_from, int timestamp_to) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List getActivitySamples(int timestamp_from, int timestamp_to) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List getSleepSamples(int timestamp_from, int timestamp_to) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeStoredSamplesType(int timestampFrom, int timestampTo, int kind) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeStoredSamplesType(int timestampFrom, int timestampTo, int fromKind, int toKind) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int fetchLatestTimestamp() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGBActivitySample(AbstractActivitySample activitySample) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addGBActivitySamples(AbstractActivitySample[] activitySamples) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractActivitySample createActivitySample() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getID() {
|
||||
return PROVIDER_UNKNOWN;
|
||||
@ -65,8 +111,8 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleProvider getSampleProvider() {
|
||||
return sampleProvider;
|
||||
public SampleProvider<?> getSampleProvider(DaoSession session) {
|
||||
return new UnknownSampleProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -93,4 +139,9 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
|
||||
public int getTapString() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
@ -10,21 +10,23 @@ import org.slf4j.LoggerFactory;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class MiBandCoordinator extends AbstractDeviceCoordinator {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(MiBandCoordinator.class);
|
||||
private final MiBandSampleProvider sampleProvider;
|
||||
|
||||
public MiBandCoordinator() {
|
||||
sampleProvider = new MiBandSampleProvider();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -54,8 +56,8 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleProvider getSampleProvider() {
|
||||
return sampleProvider;
|
||||
public SampleProvider<? extends AbstractActivitySample> getSampleProvider(DaoSession session) {
|
||||
return new MiBandSampleProvider(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -84,6 +86,11 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
|
||||
return R.string.tap_connected_device_for_activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Xiaomi";
|
||||
}
|
||||
|
||||
public static boolean hasValidUserInfo() {
|
||||
String dummyMacAddress = MiBandService.MAC_ADDRESS_FILTER_1_1A + ":00:00:00";
|
||||
try {
|
||||
@ -122,10 +129,10 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
|
||||
UserInfo info = UserInfo.create(
|
||||
miBandAddress,
|
||||
prefs.getString(MiBandConst.PREF_USER_ALIAS, null),
|
||||
activityUser.getActivityUserGender(),
|
||||
activityUser.getActivityUserAge(),
|
||||
activityUser.getActivityUserHeightCm(),
|
||||
activityUser.getActivityUserWeightKg(),
|
||||
activityUser.getGender(),
|
||||
activityUser.getAge(),
|
||||
activityUser.getHeightCm(),
|
||||
activityUser.getWeightKg(),
|
||||
0
|
||||
);
|
||||
return info;
|
||||
|
@ -8,7 +8,7 @@ import android.support.v4.content.LocalBroadcastManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_GENERIC;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_INCOMING_CALL;
|
||||
@ -17,14 +17,11 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.OR
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_SMS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ADDRESS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_DONT_ACK_TRANSFER;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_FITNESS_GOAL;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_WEARSIDE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_USER_ALIAS;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_COUNT;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_PROFILE;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.getNotificationPrefKey;
|
||||
|
||||
public class MiBandPreferencesActivity extends AbstractSettingsActivity {
|
||||
@ -38,7 +35,7 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
|
||||
developmentMiaddr.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newVal) {
|
||||
Intent refreshIntent = new Intent(ControlCenter.ACTION_REFRESH_DEVICELIST);
|
||||
Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
|
||||
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(refreshIntent);
|
||||
preference.setSummary(newVal.toString());
|
||||
return true;
|
||||
|
@ -1,9 +1,15 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.miband;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
import de.greenrobot.dao.Property;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySampleDao;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
|
||||
public class MiBandSampleProvider implements SampleProvider {
|
||||
public class MiBandSampleProvider extends AbstractSampleProvider<MiBandActivitySample> {
|
||||
public static final int TYPE_DEEP_SLEEP = 5;
|
||||
public static final int TYPE_LIGHT_SLEEP = 4;
|
||||
public static final int TYPE_ACTIVITY = -1;
|
||||
@ -22,6 +28,10 @@ public class MiBandSampleProvider implements SampleProvider {
|
||||
// maybe this should be configurable 256 seems way off, though.
|
||||
private final float movementDivisor = 180.0f; //256.0f;
|
||||
|
||||
public MiBandSampleProvider(DaoSession session) {
|
||||
super(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int normalizeType(int rawType) {
|
||||
switch (rawType) {
|
||||
@ -67,4 +77,24 @@ public class MiBandSampleProvider implements SampleProvider {
|
||||
public int getID() {
|
||||
return SampleProvider.PROVIDER_MIBAND;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDao<MiBandActivitySample, ?> getSampleDao() {
|
||||
return getSession().getMiBandActivitySampleDao();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Property getTimestampSampleProperty() {
|
||||
return MiBandActivitySampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Property getRawKindSampleProperty() {
|
||||
return MiBandActivitySampleDao.Properties.RawKind;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MiBandActivitySample createActivitySample() {
|
||||
return new MiBandActivitySample();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,34 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
|
||||
|
||||
import de.greenrobot.dao.AbstractDao;
|
||||
import de.greenrobot.dao.Property;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleActivitySampleDao;
|
||||
|
||||
public abstract class AbstractPebbleSampleProvider extends AbstractSampleProvider<PebbleActivitySample> {
|
||||
protected AbstractPebbleSampleProvider(DaoSession session) {
|
||||
super(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDao<PebbleActivitySample, ?> getSampleDao() {
|
||||
return getSession().getPebbleActivitySampleDao();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Property getTimestampSampleProperty() {
|
||||
return PebbleActivitySampleDao.Properties.Timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Property getRawKindSampleProperty() {
|
||||
return PebbleActivitySampleDao.Properties.RawKind;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PebbleActivitySample createActivitySample() {
|
||||
return new PebbleActivitySample();
|
||||
}
|
||||
}
|
@ -1,15 +1,21 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
|
||||
public class HealthSampleProvider implements SampleProvider {
|
||||
public class HealthSampleProvider extends AbstractPebbleSampleProvider {
|
||||
public static final int TYPE_DEEP_SLEEP = 5;
|
||||
public static final int TYPE_LIGHT_SLEEP = 4;
|
||||
public static final int TYPE_ACTIVITY = -1;
|
||||
|
||||
protected final float movementDivisor = 8000f;
|
||||
|
||||
public HealthSampleProvider(DaoSession session) {
|
||||
super(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int normalizeType(int rawType) {
|
||||
switch (rawType) {
|
||||
@ -38,13 +44,11 @@ public class HealthSampleProvider implements SampleProvider {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public float normalizeIntensity(int rawIntensity) {
|
||||
return rawIntensity / movementDivisor;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getID() {
|
||||
return SampleProvider.PROVIDER_PEBBLE_HEALTH;
|
||||
|
@ -1,14 +1,19 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
|
||||
public class MisfitSampleProvider implements SampleProvider {
|
||||
public class MisfitSampleProvider extends AbstractPebbleSampleProvider {
|
||||
|
||||
protected final float movementDivisor = 300f;
|
||||
|
||||
public MisfitSampleProvider(DaoSession session) {
|
||||
super(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int normalizeType(int rawType) {
|
||||
return (int) rawType;
|
||||
return rawType;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,9 +1,10 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
|
||||
public class MorpheuzSampleProvider implements SampleProvider {
|
||||
public class MorpheuzSampleProvider extends AbstractPebbleSampleProvider {
|
||||
// raw types
|
||||
public static final int TYPE_DEEP_SLEEP = 5;
|
||||
public static final int TYPE_LIGHT_SLEEP = 4;
|
||||
@ -12,6 +13,10 @@ public class MorpheuzSampleProvider implements SampleProvider {
|
||||
|
||||
protected float movementDivisor = 5000f;
|
||||
|
||||
public MorpheuzSampleProvider(DaoSession session) {
|
||||
super(session);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int normalizeType(int rawType) {
|
||||
switch (rawType) {
|
||||
|
@ -10,6 +10,8 @@ import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AppManagerActi
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
@ -44,20 +46,20 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public SampleProvider getSampleProvider() {
|
||||
public SampleProvider<? extends AbstractActivitySample> getSampleProvider(DaoSession session) {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
int activityTracker = prefs.getInt("pebble_activitytracker", SampleProvider.PROVIDER_PEBBLE_HEALTH);
|
||||
switch (activityTracker) {
|
||||
case SampleProvider.PROVIDER_PEBBLE_HEALTH:
|
||||
return new HealthSampleProvider();
|
||||
return new HealthSampleProvider(session);
|
||||
case SampleProvider.PROVIDER_PEBBLE_MISFIT:
|
||||
return new MisfitSampleProvider();
|
||||
return new MisfitSampleProvider(session);
|
||||
case SampleProvider.PROVIDER_PEBBLE_MORPHEUZ:
|
||||
return new MorpheuzSampleProvider();
|
||||
return new MorpheuzSampleProvider(session);
|
||||
case SampleProvider.PROVIDER_PEBBLE_GADGETBRIDGE:
|
||||
return new PebbleGadgetBridgeSampleProvider();
|
||||
return new PebbleGadgetBridgeSampleProvider(session);
|
||||
default:
|
||||
return new HealthSampleProvider();
|
||||
return new HealthSampleProvider(session);
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,4 +88,9 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
|
||||
public int getTapString() {
|
||||
return R.string.tap_connected_device_for_app_mananger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getManufacturer() {
|
||||
return "Pebble";
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,11 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.pebble;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
|
||||
public class PebbleGadgetBridgeSampleProvider extends MorpheuzSampleProvider {
|
||||
public PebbleGadgetBridgeSampleProvider() {
|
||||
public PebbleGadgetBridgeSampleProvider(DaoSession session) {
|
||||
super(session);
|
||||
movementDivisor = 63.0f;
|
||||
}
|
||||
|
||||
|
1
app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/.gitignore
vendored
Normal file
1
app/src/main/java/nodomain/freeyourgadget/gadgetbridge/entities/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.java
|
@ -0,0 +1,56 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.entities;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
|
||||
public abstract class AbstractActivitySample implements ActivitySample {
|
||||
private SampleProvider mProvider;
|
||||
|
||||
@Override
|
||||
public SampleProvider getProvider() {
|
||||
return mProvider;
|
||||
}
|
||||
|
||||
public void setProvider(SampleProvider provider) {
|
||||
mProvider = provider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getKind() {
|
||||
return getProvider().normalizeType(getRawKind());
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getIntensity() {
|
||||
return getProvider().normalizeIntensity(getRawIntensity());
|
||||
}
|
||||
|
||||
public abstract void setRawKind(int kind);
|
||||
|
||||
public abstract void setRawIntensity(int intensity);
|
||||
|
||||
public abstract void setSteps(int steps);
|
||||
|
||||
public abstract void setTimestamp(int timestamp);
|
||||
|
||||
public abstract void setUserId(Long userId);
|
||||
|
||||
public abstract Long getUserId();
|
||||
|
||||
public abstract void setDeviceId(Long deviceId);
|
||||
|
||||
public abstract Long getDeviceId();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "{" +
|
||||
"timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimeStamp(getTimestamp())) +
|
||||
", intensity=" + getIntensity() +
|
||||
", steps=" + getSteps() +
|
||||
", type=" + getKind() +
|
||||
", userId=" + getUserId() +
|
||||
", deviceId=" + getDeviceId() +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -7,7 +7,7 @@ import android.content.Intent;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class BluetoothStateChangeReceiver extends BroadcastReceiver {
|
||||
@ -18,7 +18,7 @@ public class BluetoothStateChangeReceiver extends BroadcastReceiver {
|
||||
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
|
||||
if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_ON) {
|
||||
|
||||
Intent refreshIntent = new Intent(ControlCenter.ACTION_REFRESH_DEVICELIST);
|
||||
Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
|
||||
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
|
@ -4,6 +4,9 @@ import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
|
||||
/**
|
||||
* Can be removed entirely together with ActivityDatabaseHandler
|
||||
*/
|
||||
public class GBActivitySample implements ActivitySample {
|
||||
private final int timestamp;
|
||||
private final SampleProvider provider;
|
||||
@ -76,18 +79,12 @@ public class GBActivitySample implements ActivitySample {
|
||||
return getProvider().normalizeType(getRawKind());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCustomValue() {
|
||||
return customValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GBActivitySample{" +
|
||||
"timestamp=" + DateTimeUtils.formatDateTime(DateTimeUtils.parseTimeStamp(timestamp)) +
|
||||
", intensity=" + getIntensity() +
|
||||
", steps=" + getSteps() +
|
||||
", customValue=" + getCustomValue() +
|
||||
", type=" + getKind() +
|
||||
'}';
|
||||
}
|
||||
|
@ -46,8 +46,9 @@ public class GBDevice implements Parcelable {
|
||||
private final String mName;
|
||||
private final String mAddress;
|
||||
private final DeviceType mDeviceType;
|
||||
private String mFirmwareVersion = null;
|
||||
private String mHardwareVersion = null;
|
||||
private String mFirmwareVersion;
|
||||
private String mFirmwareVersion2;
|
||||
private String mHardwareVersion;
|
||||
private State mState = State.NOT_CONNECTED;
|
||||
private short mBatteryLevel = BATTERY_UNKNOWN;
|
||||
private short mBatteryThresholdPercent = BATTERY_THRESHOLD_PERCENT;
|
||||
@ -68,6 +69,7 @@ public class GBDevice implements Parcelable {
|
||||
mAddress = in.readString();
|
||||
mDeviceType = DeviceType.values()[in.readInt()];
|
||||
mFirmwareVersion = in.readString();
|
||||
mFirmwareVersion2 = in.readString();
|
||||
mHardwareVersion = in.readString();
|
||||
mState = State.values()[in.readInt()];
|
||||
mBatteryLevel = (short) in.readInt();
|
||||
@ -86,6 +88,7 @@ public class GBDevice implements Parcelable {
|
||||
dest.writeString(mAddress);
|
||||
dest.writeInt(mDeviceType.ordinal());
|
||||
dest.writeString(mFirmwareVersion);
|
||||
dest.writeString(mFirmwareVersion2);
|
||||
dest.writeString(mHardwareVersion);
|
||||
dest.writeInt(mState.ordinal());
|
||||
dest.writeInt(mBatteryLevel);
|
||||
@ -113,11 +116,18 @@ public class GBDevice implements Parcelable {
|
||||
public String getFirmwareVersion() {
|
||||
return mFirmwareVersion;
|
||||
}
|
||||
public String getFirmwareVersion2() {
|
||||
return mFirmwareVersion2;
|
||||
}
|
||||
|
||||
public void setFirmwareVersion(String firmwareVersion) {
|
||||
mFirmwareVersion = firmwareVersion;
|
||||
}
|
||||
|
||||
public void setFirmwareVersion2(String firmwareVersion2) {
|
||||
mFirmwareVersion2 = firmwareVersion2;
|
||||
}
|
||||
|
||||
public String getHardwareVersion() {
|
||||
return mHardwareVersion;
|
||||
}
|
||||
@ -336,6 +346,15 @@ public class GBDevice implements Parcelable {
|
||||
return getDeviceInfos().size() > 0;
|
||||
}
|
||||
|
||||
public ItemWithDetails getDeviceInfo(String name) {
|
||||
for (ItemWithDetails item : getDeviceInfos()) {
|
||||
if (name.equals(item.getName())) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<ItemWithDetails> getDeviceInfos() {
|
||||
List<ItemWithDetails> result = new ArrayList<>();
|
||||
if (mDeviceInfos != null) {
|
||||
|
@ -2,7 +2,7 @@ package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
|
||||
public interface ActivitySample {
|
||||
public interface ActivitySample extends TimeStamped {
|
||||
/**
|
||||
* Returns the provider of the data.
|
||||
*
|
||||
@ -10,11 +10,6 @@ public interface ActivitySample {
|
||||
*/
|
||||
SampleProvider getProvider();
|
||||
|
||||
/**
|
||||
* Timestamp of the sample, resolution is seconds!
|
||||
*/
|
||||
int getTimestamp();
|
||||
|
||||
/**
|
||||
* Returns the raw activity kind value as recorded by the SampleProvider
|
||||
*/
|
||||
@ -41,6 +36,4 @@ public interface ActivitySample {
|
||||
* Returns the number of steps performed during the period of this sample
|
||||
*/
|
||||
int getSteps();
|
||||
|
||||
int getCustomValue();
|
||||
}
|
||||
|
@ -1,8 +1,12 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
/**
|
||||
@ -10,50 +14,52 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
*/
|
||||
public class ActivityUser {
|
||||
|
||||
private Integer activityUserGender;
|
||||
private Integer activityUserYearOfBirth;
|
||||
private Integer activityUserHeightCm;
|
||||
private Integer activityUserWeightKg;
|
||||
private Integer activityUserSleepDuration;
|
||||
private String activityUserName;
|
||||
private int activityUserGender;
|
||||
private int activityUserYearOfBirth;
|
||||
private int activityUserHeightCm;
|
||||
private int activityUserWeightKg;
|
||||
private int activityUserSleepDuration;
|
||||
private int activityUserStepsGoal;
|
||||
|
||||
private static final String defaultUserName = "gadgetbridge-user";
|
||||
public static final int defaultUserGender = 0;
|
||||
public static final int defaultUserYearOfBirth = 0;
|
||||
public static final int defaultUserAge = 0;
|
||||
public static final int defaultUserHeightCm = 175;
|
||||
public static final int defaultUserWeightKg = 70;
|
||||
public static final int defaultUserSleepDuration = 7;
|
||||
public static final int defaultUserStepsGoal = 8000;
|
||||
|
||||
public static final String PREF_USER_NAME = "mi_user_alias";
|
||||
public static final String PREF_USER_YEAR_OF_BIRTH = "activity_user_year_of_birth";
|
||||
public static final String PREF_USER_GENDER = "activity_user_gender";
|
||||
public static final String PREF_USER_HEIGHT_CM = "activity_user_height_cm";
|
||||
public static final String PREF_USER_WEIGHT_KG = "activity_user_weight_kg";
|
||||
public static final String PREF_USER_SLEEP_DURATION = "activity_user_sleep_duration";
|
||||
public static final String PREF_USER_STEPS_GOAL = MiBandConst.PREF_MIBAND_FITNESS_GOAL;
|
||||
|
||||
public int getActivityUserWeightKg() {
|
||||
if (activityUserWeightKg == null) {
|
||||
fetchPreferences();
|
||||
}
|
||||
public ActivityUser() {
|
||||
fetchPreferences();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return activityUserName;
|
||||
}
|
||||
|
||||
public int getWeightKg() {
|
||||
return activityUserWeightKg;
|
||||
}
|
||||
|
||||
public int getActivityUserGender() {
|
||||
if (activityUserGender == null) {
|
||||
fetchPreferences();
|
||||
}
|
||||
public int getGender() {
|
||||
return activityUserGender;
|
||||
}
|
||||
|
||||
public int getActivityUserYearOfBirth() {
|
||||
if (activityUserYearOfBirth == null) {
|
||||
fetchPreferences();
|
||||
}
|
||||
public int getYearOfBirth() {
|
||||
return activityUserYearOfBirth;
|
||||
}
|
||||
|
||||
public int getActivityUserHeightCm() {
|
||||
if (activityUserHeightCm == null) {
|
||||
fetchPreferences();
|
||||
}
|
||||
public int getHeightCm() {
|
||||
return activityUserHeightCm;
|
||||
}
|
||||
|
||||
@ -61,18 +67,22 @@ public class ActivityUser {
|
||||
* @return the user defined sleep duration or the default value when none is set or the stored
|
||||
* value is out of any logical bounds.
|
||||
*/
|
||||
public int getActivityUserSleepDuration() {
|
||||
if (activityUserSleepDuration == null) {
|
||||
fetchPreferences();
|
||||
}
|
||||
public int getSleepDuration() {
|
||||
if (activityUserSleepDuration < 1 || activityUserSleepDuration > 24) {
|
||||
activityUserSleepDuration = defaultUserSleepDuration;
|
||||
}
|
||||
return activityUserSleepDuration;
|
||||
}
|
||||
|
||||
public int getActivityUserAge() {
|
||||
int userYear = getActivityUserYearOfBirth();
|
||||
public int getStepsGoal() {
|
||||
if (activityUserStepsGoal < 0) {
|
||||
activityUserStepsGoal = defaultUserStepsGoal;
|
||||
}
|
||||
return activityUserStepsGoal;
|
||||
}
|
||||
|
||||
public int getAge() {
|
||||
int userYear = getYearOfBirth();
|
||||
int age = 25;
|
||||
if (userYear > 1900) {
|
||||
age = Calendar.getInstance().get(Calendar.YEAR) - userYear;
|
||||
@ -85,10 +95,18 @@ public class ActivityUser {
|
||||
|
||||
private void fetchPreferences() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
activityUserName = prefs.getString(PREF_USER_NAME, defaultUserName);
|
||||
activityUserGender = prefs.getInt(PREF_USER_GENDER, defaultUserGender);
|
||||
activityUserHeightCm = prefs.getInt(PREF_USER_HEIGHT_CM, defaultUserHeightCm);
|
||||
activityUserWeightKg = prefs.getInt(PREF_USER_WEIGHT_KG, defaultUserWeightKg);
|
||||
activityUserYearOfBirth = prefs.getInt(PREF_USER_YEAR_OF_BIRTH, defaultUserYearOfBirth);
|
||||
activityUserSleepDuration = prefs.getInt(PREF_USER_SLEEP_DURATION, defaultUserSleepDuration);
|
||||
activityUserStepsGoal = prefs.getInt(PREF_USER_STEPS_GOAL, defaultUserStepsGoal);
|
||||
}
|
||||
|
||||
public Date getUserBirthday() {
|
||||
Calendar cal = DateTimeUtils.getCalendarUTC();
|
||||
cal.set(GregorianCalendar.YEAR, getYearOfBirth());
|
||||
return cal.getTime();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
public interface HeartRateSample extends TimeStamped {
|
||||
/**
|
||||
* Returns the heart rate measured at the corresponding timestamp.
|
||||
* The value is returned in heart beats per minute, in the range from
|
||||
* 0-255, where 255 is an illegal value (e.g. due to a bad measurement)
|
||||
* @return the heart rate value in beats per minute, or null if none
|
||||
*/
|
||||
Integer getHeartRate();
|
||||
|
||||
/**
|
||||
* Sets the heart rate value of this sample. Typically only used in
|
||||
* generic db migration.
|
||||
* @param value the value in bpm
|
||||
*/
|
||||
void setHeartRate(Integer value);
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
public interface TimeStamped {
|
||||
/**
|
||||
* Timestamp of the sample, resolution is seconds!
|
||||
*/
|
||||
int getTimestamp();
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public interface ValidByDate {
|
||||
Date getValidFromUTC();
|
||||
Date getValidToUTC();
|
||||
}
|
@ -2,7 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations;
|
||||
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@ -18,13 +17,12 @@ import java.util.concurrent.TimeUnit;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandDateConverter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceBusyAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.MiBandSupport;
|
||||
@ -306,18 +304,18 @@ public class FetchActivityOperation extends AbstractMiBandOperation {
|
||||
LOG.debug("flushing activity data samples: " + activityStruct.activityDataHolderProgress / bpm);
|
||||
byte category, intensity, steps, heartrate = 0;
|
||||
|
||||
DBHandler dbHandler = null;
|
||||
try {
|
||||
dbHandler = GBApplication.acquireDB();
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()){
|
||||
MiBandSampleProvider provider = new MiBandSampleProvider(dbHandler.getDaoSession());
|
||||
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
|
||||
Long deviceId = DBHelper.getDevice(getDevice(), dbHandler.getDaoSession()).getId();
|
||||
int minutes = 0;
|
||||
try (SQLiteDatabase db = dbHandler.getWritableDatabase()) { // explicitly keep the db open while looping over the samples
|
||||
try {
|
||||
int timestampInSeconds = (int) (activityStruct.activityDataTimestampProgress.getTimeInMillis() / 1000);
|
||||
if ((activityStruct.activityDataHolderProgress % bpm) != 0) {
|
||||
throw new IllegalStateException("Unexpected data, progress should be mutiple of " + bpm + ": " + activityStruct.activityDataHolderProgress);
|
||||
}
|
||||
int numSamples = activityStruct.activityDataHolderProgress / bpm;
|
||||
ActivitySample[] samples = new ActivitySample[numSamples];
|
||||
SampleProvider sampleProvider = new MiBandSampleProvider();
|
||||
MiBandActivitySample[] samples = new MiBandActivitySample[numSamples];
|
||||
|
||||
for (int i = 0; i < activityStruct.activityDataHolderProgress; i += bpm) {
|
||||
category = activityStruct.activityDataHolder[i];
|
||||
@ -328,28 +326,27 @@ public class FetchActivityOperation extends AbstractMiBandOperation {
|
||||
LOG.debug("heartrate received: " + (heartrate & 0xff));
|
||||
}
|
||||
|
||||
samples[minutes] = new GBActivitySample(
|
||||
sampleProvider,
|
||||
samples[minutes] = new MiBandActivitySample(
|
||||
null,
|
||||
timestampInSeconds,
|
||||
intensity & 0xff,
|
||||
steps & 0xff,
|
||||
category & 0xff,
|
||||
userId,
|
||||
deviceId,
|
||||
heartrate & 0xff);
|
||||
// samples[minutes].setProvider(dbHandler);
|
||||
|
||||
// next minute
|
||||
minutes++;
|
||||
timestampInSeconds += 60;
|
||||
}
|
||||
dbHandler.addGBActivitySamples(samples);
|
||||
provider.addGBActivitySamples(samples);
|
||||
} finally {
|
||||
activityStruct.bufferFlushed(minutes);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
GB.toast(getContext(), ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
} finally {
|
||||
if (dbHandler != null) {
|
||||
dbHandler.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,13 @@ import android.util.Pair;
|
||||
import java.util.ArrayList;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
|
||||
public class AppMessageHandler {
|
||||
protected final PebbleProtocol mPebbleProtocol;
|
||||
@ -32,4 +38,12 @@ public class AppMessageHandler {
|
||||
public GBDeviceEvent[] pushMessage() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected PebbleActivitySample createSample(int timestamp, int intensity, int steps, int type, User user, Device device) {
|
||||
return new PebbleActivitySample(null, timestamp, intensity, steps, type, user.getId(), device.getId());
|
||||
}
|
||||
|
||||
protected GBDevice getDevice() {
|
||||
return mPebbleProtocol.getDevice();
|
||||
}
|
||||
}
|
@ -15,9 +15,17 @@ import java.util.UUID;
|
||||
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.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleGadgetBridgeSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
|
||||
public class AppMessageHandlerGBPebble extends AppMessageHandler {
|
||||
|
||||
@ -47,23 +55,23 @@ public class AppMessageHandlerGBPebble extends AppMessageHandler {
|
||||
int samples_remaining = samples.length / 2;
|
||||
LOG.info("got " + samples_remaining + " samples");
|
||||
int offset_seconds = 0;
|
||||
DBHandler db = null;
|
||||
try {
|
||||
db = GBApplication.acquireDB();
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
User user = DBHelper.getUser(db.getDaoSession());
|
||||
Device device = DBHelper.getDevice(getDevice(), db.getDaoSession());
|
||||
PebbleGadgetBridgeSampleProvider sampleProvider = new PebbleGadgetBridgeSampleProvider(db.getDaoSession());
|
||||
PebbleActivitySample[] activitySamples = new PebbleActivitySample[samples_remaining];
|
||||
int i = 0;
|
||||
while (samples_remaining-- > 0) {
|
||||
short sample = samplesBuffer.getShort();
|
||||
int type = ((sample & 0xe000) >>> 13);
|
||||
int intensity = ((sample & 0x1f80) >>> 7);
|
||||
int steps = (sample & 0x007f);
|
||||
db.addGBActivitySample(timestamp + offset_seconds, SampleProvider.PROVIDER_PEBBLE_GADGETBRIDGE, intensity, steps, type, 0);
|
||||
activitySamples[i++] = createSample(timestamp + offset_seconds, intensity, steps, type, user, device);
|
||||
offset_seconds += 60;
|
||||
}
|
||||
} catch (GBException e) {
|
||||
sampleProvider.addGBActivitySamples(activitySamples);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error acquiring database", e);
|
||||
} finally {
|
||||
if (db != null) {
|
||||
db.release();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -15,9 +15,12 @@ import java.util.UUID;
|
||||
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.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.MisfitSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
@ -40,8 +43,6 @@ public class AppMessageHandlerMisfit extends AppMessageHandler {
|
||||
super(uuid, pebbleProtocol);
|
||||
}
|
||||
|
||||
private final MisfitSampleProvider sampleProvider = new MisfitSampleProvider();
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
Prefs prefs = GBApplication.getPrefs();
|
||||
@ -77,52 +78,49 @@ public class AppMessageHandlerMisfit extends AppMessageHandler {
|
||||
LOG.info("got data from " + startDate + " to " + endDate);
|
||||
|
||||
int totalSteps = 0;
|
||||
GBActivitySample[] activitySamples = new GBActivitySample[samples];
|
||||
for (int i = 0; i < samples; i++) {
|
||||
short sample = buf.getShort();
|
||||
int steps = 0;
|
||||
int intensity = 0;
|
||||
int activityKind = ActivityKind.TYPE_UNKNOWN;
|
||||
PebbleActivitySample[] activitySamples = new PebbleActivitySample[samples];
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
Long userId = DBHelper.getUser(db.getDaoSession()).getId();
|
||||
Long deviceId = DBHelper.getDevice(getDevice(), db.getDaoSession()).getId();
|
||||
for (int i = 0; i < samples; i++) {
|
||||
short sample = buf.getShort();
|
||||
int steps = 0;
|
||||
int intensity = 0;
|
||||
int activityKind = ActivityKind.TYPE_UNKNOWN;
|
||||
|
||||
if (((sample & 0x83ff) == 0x0001) && ((sample & 0xff00) <= 0x4800)) {
|
||||
// sleep seems to be from 0x2401 to 0x4801 (0b0IIIII0000000001) where I = intensity ?
|
||||
intensity = (sample & 0x7c00) >>> 10;
|
||||
// 9-18 decimal after shift
|
||||
if (intensity <= 13) {
|
||||
activityKind = ActivityKind.TYPE_DEEP_SLEEP;
|
||||
if (((sample & 0x83ff) == 0x0001) && ((sample & 0xff00) <= 0x4800)) {
|
||||
// sleep seems to be from 0x2401 to 0x4801 (0b0IIIII0000000001) where I = intensity ?
|
||||
intensity = (sample & 0x7c00) >>> 10;
|
||||
// 9-18 decimal after shift
|
||||
if (intensity <= 13) {
|
||||
activityKind = ActivityKind.TYPE_DEEP_SLEEP;
|
||||
} else {
|
||||
// FIXME: this leads to too much false positives, ignore for now
|
||||
//activityKind = ActivityKind.TYPE_LIGHT_SLEEP;
|
||||
//intensity *= 2; // better visual distinction
|
||||
}
|
||||
} else {
|
||||
// FIXME: this leads to too much false positives, ignore for now
|
||||
//activityKind = ActivityKind.TYPE_LIGHT_SLEEP;
|
||||
//intensity *= 2; // better visual distinction
|
||||
if ((sample & 0x0001) == 0) { // 16-??? steps encoded in bits 1-7
|
||||
steps = (sample & 0x00fe);
|
||||
} else { // 0-14 steps encoded in bits 1-3, most of the time fc71 bits are set in that case
|
||||
steps = (sample & 0x000e);
|
||||
}
|
||||
intensity = steps;
|
||||
activityKind = ActivityKind.TYPE_ACTIVITY;
|
||||
}
|
||||
} else {
|
||||
if ((sample & 0x0001) == 0) { // 16-??? steps encoded in bits 1-7
|
||||
steps = (sample & 0x00fe);
|
||||
} else { // 0-14 steps encoded in bits 1-3, most of the time fc71 bits are set in that case
|
||||
steps = (sample & 0x000e);
|
||||
}
|
||||
intensity = steps;
|
||||
activityKind = ActivityKind.TYPE_ACTIVITY;
|
||||
|
||||
totalSteps += steps;
|
||||
LOG.info("got steps for sample " + i + " : " + steps + "(" + Integer.toHexString(sample & 0xffff) + ")");
|
||||
|
||||
activitySamples[i] = new PebbleActivitySample(null, timestamp + i * 60, intensity, steps, activityKind, userId, deviceId);
|
||||
}
|
||||
LOG.info("total steps for above period: " + totalSteps);
|
||||
|
||||
totalSteps += steps;
|
||||
LOG.info("got steps for sample " + i + " : " + steps + "(" + Integer.toHexString(sample & 0xffff) + ")");
|
||||
|
||||
activitySamples[i] = new GBActivitySample(sampleProvider, timestamp + i * 60, intensity, steps, activityKind);
|
||||
}
|
||||
LOG.info("total steps for above period: " + totalSteps);
|
||||
|
||||
DBHandler db = null;
|
||||
try {
|
||||
db = GBApplication.acquireDB();
|
||||
db.addGBActivitySamples(activitySamples);
|
||||
} catch (GBException e) {
|
||||
MisfitSampleProvider sampleProvider = new MisfitSampleProvider(db.getDaoSession());
|
||||
sampleProvider.addGBActivitySamples(activitySamples);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error acquiring database", e);
|
||||
return null;
|
||||
} finally {
|
||||
if (db != null) {
|
||||
db.release();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -11,13 +11,15 @@ import java.util.TimeZone;
|
||||
import java.util.UUID;
|
||||
|
||||
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.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSleepMonitorResult;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.MorpheuzSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.User;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class AppMessageHandlerMorpheuz extends AppMessageHandler {
|
||||
@ -97,16 +99,13 @@ public class AppMessageHandlerMorpheuz extends AppMessageHandler {
|
||||
type = MorpheuzSampleProvider.TYPE_LIGHT_SLEEP;
|
||||
}
|
||||
if (index >= 0) {
|
||||
DBHandler db = null;
|
||||
try {
|
||||
db = GBApplication.acquireDB();
|
||||
db.addGBActivitySample(recording_base_timestamp + index * 600, SampleProvider.PROVIDER_PEBBLE_MORPHEUZ, intensity, 0, type, 0);
|
||||
} catch (GBException e) {
|
||||
try (DBHandler db = GBApplication.acquireDB()) {
|
||||
User user = DBHelper.getUser(db.getDaoSession());
|
||||
Device device = DBHelper.getDevice(getDevice(), db.getDaoSession());
|
||||
MorpheuzSampleProvider sampleProvider = new MorpheuzSampleProvider(db.getDaoSession());
|
||||
sampleProvider.addGBActivitySample(createSample(recording_base_timestamp + index * 600, intensity, 0, type, user, device));
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error acquiring database", e);
|
||||
} finally {
|
||||
if (db != null) {
|
||||
db.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,20 +58,18 @@ class DatalogSessionHealthOverlayData extends DatalogSessionPebbleHealth {
|
||||
}
|
||||
|
||||
private boolean store(OverlayRecord[] overlayRecords) {
|
||||
DBHandler dbHandler = null;
|
||||
SampleProvider sampleProvider = new HealthSampleProvider();
|
||||
try {
|
||||
dbHandler = GBApplication.acquireDB();
|
||||
int latestTimestamp = dbHandler.fetchLatestTimestamp(sampleProvider);
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
SampleProvider sampleProvider = new HealthSampleProvider(dbHandler.getDaoSession());
|
||||
int latestTimestamp = sampleProvider.fetchLatestTimestamp();
|
||||
for (OverlayRecord overlayRecord : overlayRecords) {
|
||||
if (latestTimestamp < (overlayRecord.timestampStart + overlayRecord.durationSeconds))
|
||||
return false;
|
||||
switch (overlayRecord.type) {
|
||||
case 1:
|
||||
dbHandler.changeStoredSamplesType(overlayRecord.timestampStart, (overlayRecord.timestampStart + overlayRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP), sampleProvider);
|
||||
sampleProvider.changeStoredSamplesType(overlayRecord.timestampStart, (overlayRecord.timestampStart + overlayRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP));
|
||||
break;
|
||||
case 2:
|
||||
dbHandler.changeStoredSamplesType(overlayRecord.timestampStart, (overlayRecord.timestampStart + overlayRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_DEEP_SLEEP), sampleProvider);
|
||||
sampleProvider.changeStoredSamplesType(overlayRecord.timestampStart, (overlayRecord.timestampStart + overlayRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_DEEP_SLEEP));
|
||||
break;
|
||||
default:
|
||||
//TODO: other values refer to unknown activity types.
|
||||
@ -79,10 +77,6 @@ class DatalogSessionHealthOverlayData extends DatalogSessionPebbleHealth {
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOG.debug(ex.getMessage());
|
||||
} finally {
|
||||
if (dbHandler != null) {
|
||||
dbHandler.release();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -57,22 +57,16 @@ class DatalogSessionHealthSleep extends DatalogSessionPebbleHealth {
|
||||
}
|
||||
|
||||
private boolean store(SleepRecord[] sleepRecords) {
|
||||
DBHandler dbHandler = null;
|
||||
SampleProvider sampleProvider = new HealthSampleProvider();
|
||||
try {
|
||||
dbHandler = GBApplication.acquireDB();
|
||||
int latestTimestamp = dbHandler.fetchLatestTimestamp(sampleProvider);
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
SampleProvider sampleProvider = new HealthSampleProvider(dbHandler.getDaoSession());
|
||||
int latestTimestamp = sampleProvider.fetchLatestTimestamp();
|
||||
for (SleepRecord sleepRecord : sleepRecords) {
|
||||
if (latestTimestamp < sleepRecord.bedTimeEnd)
|
||||
return false;
|
||||
dbHandler.changeStoredSamplesType(sleepRecord.bedTimeStart, sleepRecord.bedTimeEnd, sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP), sampleProvider);
|
||||
sampleProvider.changeStoredSamplesType(sleepRecord.bedTimeStart, sleepRecord.bedTimeEnd, sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOG.debug(ex.getMessage());
|
||||
} finally {
|
||||
if (dbHandler != null) {
|
||||
dbHandler.release();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -9,9 +9,13 @@ import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.HealthSampleProvider;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.PebbleActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
@ -19,10 +23,12 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
public class DatalogSessionHealthSteps extends DatalogSessionPebbleHealth {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthSteps.class);
|
||||
private final GBDevice device;
|
||||
|
||||
public DatalogSessionHealthSteps(byte id, UUID uuid, int tag, byte item_type, short item_size) {
|
||||
public DatalogSessionHealthSteps(byte id, UUID uuid, int tag, byte item_type, short item_size, GBDevice device) {
|
||||
super(id, uuid, tag, item_type, item_size);
|
||||
taginfo = "(health - steps)";
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -74,29 +80,26 @@ public class DatalogSessionHealthSteps extends DatalogSessionPebbleHealth {
|
||||
|
||||
private void store(StepsRecord[] stepsRecords) {
|
||||
|
||||
DBHandler dbHandler = null;
|
||||
SampleProvider sampleProvider = new HealthSampleProvider();
|
||||
try (DBHandler dbHandler = GBApplication.acquireDB()) {
|
||||
HealthSampleProvider sampleProvider = new HealthSampleProvider(dbHandler.getDaoSession());
|
||||
PebbleActivitySample[] samples = new PebbleActivitySample[stepsRecords.length];
|
||||
// TODO: user and device
|
||||
Long userId = DBHelper.getUser(dbHandler.getDaoSession()).getId();
|
||||
Long deviceId = DBHelper.getDevice(device, dbHandler.getDaoSession()).getId();
|
||||
for (int j = 0; j < stepsRecords.length; j++) {
|
||||
StepsRecord stepsRecord = stepsRecords[j];
|
||||
samples[j] = new PebbleActivitySample(
|
||||
null,
|
||||
stepsRecord.timestamp,
|
||||
stepsRecord.intensity,
|
||||
stepsRecord.steps,
|
||||
sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY),
|
||||
userId, deviceId);
|
||||
}
|
||||
|
||||
ActivitySample[] samples = new ActivitySample[stepsRecords.length];
|
||||
for (int j = 0; j < stepsRecords.length; j++) {
|
||||
StepsRecord stepsRecord = stepsRecords[j];
|
||||
samples[j] = new GBActivitySample(
|
||||
sampleProvider,
|
||||
stepsRecord.timestamp,
|
||||
stepsRecord.intensity,
|
||||
stepsRecord.steps,
|
||||
sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY));
|
||||
}
|
||||
|
||||
try {
|
||||
dbHandler = GBApplication.acquireDB();
|
||||
dbHandler.addGBActivitySamples(samples);
|
||||
sampleProvider.addGBActivitySamples(samples);
|
||||
} catch (Exception ex) {
|
||||
LOG.debug(ex.getMessage());
|
||||
} finally {
|
||||
if (dbHandler != null) {
|
||||
dbHandler.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleIconID;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
@ -370,16 +371,16 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
private static final UUID UUID_PEBSTYLE = UUID.fromString("da05e84d-e2a2-4020-a2dc-9cdcf265fcdd");
|
||||
private static final UUID UUID_ZERO = new UUID(0, 0);
|
||||
|
||||
private static final Map<UUID, AppMessageHandler> mAppMessageHandlers = new HashMap<>();
|
||||
private final Map<UUID, AppMessageHandler> mAppMessageHandlers = new HashMap<>();
|
||||
|
||||
{
|
||||
public PebbleProtocol(GBDevice device) {
|
||||
super(device);
|
||||
mAppMessageHandlers.put(UUID_GBPEBBLE, new AppMessageHandlerGBPebble(UUID_GBPEBBLE, PebbleProtocol.this));
|
||||
mAppMessageHandlers.put(UUID_MORPHEUZ, new AppMessageHandlerMorpheuz(UUID_MORPHEUZ, PebbleProtocol.this));
|
||||
mAppMessageHandlers.put(UUID_WHETHERNEAT, new AppMessageHandlerWeatherNeat(UUID_WHETHERNEAT, PebbleProtocol.this));
|
||||
mAppMessageHandlers.put(UUID_MISFIT, new AppMessageHandlerMisfit(UUID_MISFIT, PebbleProtocol.this));
|
||||
mAppMessageHandlers.put(UUID_PEBBLE_TIMESTYLE, new AppMessageHandlerTimeStylePebble(UUID_PEBBLE_TIMESTYLE, PebbleProtocol.this));
|
||||
mAppMessageHandlers.put(UUID_PEBSTYLE, new AppMessageHandlerPebStyle(UUID_PEBSTYLE, PebbleProtocol.this));
|
||||
|
||||
}
|
||||
|
||||
private final HashMap<Byte, DatalogSession> mDatalogSessions = new HashMap<>();
|
||||
@ -727,15 +728,15 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
buf.order(ByteOrder.LITTLE_ENDIAN);
|
||||
|
||||
ActivityUser activityUser = new ActivityUser();
|
||||
Integer heightMm = activityUser.getActivityUserHeightCm() * 10;
|
||||
Integer heightMm = activityUser.getHeightCm() * 10;
|
||||
buf.putShort(heightMm.shortValue());
|
||||
Integer weigthDag = activityUser.getActivityUserWeightKg() * 100;
|
||||
Integer weigthDag = activityUser.getWeightKg() * 100;
|
||||
buf.putShort(weigthDag.shortValue());
|
||||
buf.put((byte) 0x01); //activate tracking
|
||||
buf.put((byte) 0x00); //activity Insights
|
||||
buf.put((byte) 0x00); //sleep Insights
|
||||
buf.put((byte) activityUser.getActivityUserAge());
|
||||
buf.put((byte) activityUser.getActivityUserGender());
|
||||
buf.put((byte) activityUser.getAge());
|
||||
buf.put((byte) activityUser.getGender());
|
||||
blob = buf.array();
|
||||
} else {
|
||||
blob = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
|
||||
@ -1942,7 +1943,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
|
||||
LOG.info("DATALOG OPENSESSION. id=" + (id & 0xff) + ", App UUID=" + uuid.toString() + ", log_tag=" + log_tag + ", item_type=" + item_type + ", itemSize=" + item_size);
|
||||
if (!mDatalogSessions.containsKey(id)) {
|
||||
if (uuid.equals(UUID_ZERO) && log_tag == 81) {
|
||||
mDatalogSessions.put(id, new DatalogSessionHealthSteps(id, uuid, log_tag, item_type, item_size));
|
||||
mDatalogSessions.put(id, new DatalogSessionHealthSteps(id, uuid, log_tag, item_type, item_size, getDevice()));
|
||||
} else if (uuid.equals(UUID_ZERO) && log_tag == 83) {
|
||||
mDatalogSessions.put(id, new DatalogSessionHealthSleep(id, uuid, log_tag, item_type, item_size));
|
||||
} else if (uuid.equals(UUID_ZERO) && log_tag == 84) {
|
||||
|
@ -31,7 +31,7 @@ public class PebbleSupport extends AbstractSerialDeviceSupport {
|
||||
|
||||
@Override
|
||||
protected GBDeviceProtocol createDeviceProtocol() {
|
||||
return new PebbleProtocol();
|
||||
return new PebbleProtocol(getDevice());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -3,11 +3,18 @@ package nodomain.freeyourgadget.gadgetbridge.service.serial;
|
||||
import java.util.UUID;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
|
||||
public abstract class GBDeviceProtocol {
|
||||
|
||||
private GBDevice mDevice;
|
||||
|
||||
protected GBDeviceProtocol(GBDevice device) {
|
||||
mDevice = device;
|
||||
}
|
||||
|
||||
public byte[] encodeNotification(NotificationSpec notificationSpec) {
|
||||
return null;
|
||||
}
|
||||
@ -85,4 +92,8 @@ public abstract class GBDeviceProtocol {
|
||||
public GBDeviceEvent[] decodeResponse(byte[] responseData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public GBDevice getDevice() {
|
||||
return mDevice;
|
||||
}
|
||||
}
|
||||
|
@ -4,14 +4,20 @@ import android.text.format.DateUtils;
|
||||
|
||||
import com.github.pfichtner.durationformatter.DurationFormatter;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
|
||||
public class DateTimeUtils {
|
||||
private static SimpleDateFormat DAY_STORAGE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
||||
|
||||
public static String formatDateTime(Date date) {
|
||||
return DateUtils.formatDateTime(GBApplication.getContext(), date.getTime(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
|
||||
}
|
||||
@ -55,4 +61,21 @@ public class DateTimeUtils {
|
||||
cal.setTimeInMillis(timestamp * 1000L); // make sure it's converted to long
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
public static String dayToString(Date date) {
|
||||
return DAY_STORAGE_FORMAT.format(date);
|
||||
}
|
||||
|
||||
public static Date dayFromString(String day) throws ParseException {
|
||||
return DAY_STORAGE_FORMAT.parse(day);
|
||||
}
|
||||
|
||||
public static Date todayUTC() {
|
||||
Calendar cal = getCalendarUTC();
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
public static Calendar getCalendarUTC() {
|
||||
return GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import java.util.Set;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
|
||||
@ -63,6 +64,16 @@ public class DeviceHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of all available devices that are supported by Gadgetbridge.
|
||||
* Note that no state is known about the returned devices. Even if one of those
|
||||
* devices is connected, it will report the default not-connected state.
|
||||
*
|
||||
* Clients interested in the "live" devices being managed should use the class
|
||||
* DeviceManager.
|
||||
* @param context
|
||||
* @return
|
||||
*/
|
||||
public Set<GBDevice> getAvailableDevices(Context context) {
|
||||
BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
|
||||
|
@ -1,9 +1,17 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
|
||||
public class GBPrefs {
|
||||
|
||||
public static final String AUTO_RECONNECT = "general_autocreconnect";
|
||||
public static boolean AUTO_RECONNECT_DEFAULT = true;
|
||||
|
||||
public static final String USER_NAME = "mi_user_alias";
|
||||
public static final String USER_NAME_DEFAULT = "gadgetbridge-user";
|
||||
private static final String USER_BIRTHDAY = "";
|
||||
|
||||
private final Prefs mPrefs;
|
||||
|
||||
public GBPrefs(Prefs prefs) {
|
||||
@ -13,4 +21,25 @@ public class GBPrefs {
|
||||
public boolean getAutoReconnect() {
|
||||
return mPrefs.getBoolean(AUTO_RECONNECT, AUTO_RECONNECT_DEFAULT);
|
||||
}
|
||||
|
||||
public String getUserName() {
|
||||
return mPrefs.getString(USER_NAME, USER_NAME_DEFAULT);
|
||||
}
|
||||
|
||||
public Date getUserBirthday() {
|
||||
String date = mPrefs.getString(USER_BIRTHDAY, null);
|
||||
if (date == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return DateTimeUtils.dayFromString(date);
|
||||
} catch (ParseException ex) {
|
||||
GB.log("Error parsing date: " + date, GB.ERROR, ex);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public int getUserSex() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
@ -76,16 +76,6 @@
|
||||
android:layout_row="10"
|
||||
android:text="create test notification" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/setTimeButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_column="0"
|
||||
android:layout_columnSpan="3"
|
||||
android:layout_gravity="fill_horizontal"
|
||||
android:layout_row="9"
|
||||
android:text="set time" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/incomingCallButton"
|
||||
android:layout_width="wrap_content"
|
||||
@ -125,11 +115,21 @@
|
||||
android:layout_columnSpan="2" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/setMusicInfoButton"
|
||||
android:id="@+id/setTimeButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_column="0"
|
||||
android:layout_columnSpan="3"
|
||||
android:layout_columnSpan="1"
|
||||
android:layout_gravity="fill_horizontal"
|
||||
android:layout_row="5"
|
||||
android:text="set time" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/setMusicInfoButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_column="1"
|
||||
android:layout_columnSpan="2"
|
||||
android:layout_gravity="fill_horizontal"
|
||||
android:layout_row="5"
|
||||
android:text="set music info" />
|
||||
@ -179,6 +179,18 @@
|
||||
android:id="@+id/emptyDBButton"
|
||||
android:layout_row="6"
|
||||
android:layout_column="2" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/mergeOldActivityData"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_column="0"
|
||||
android:layout_columnSpan="3"
|
||||
android:layout_gravity="fill_horizontal"
|
||||
android:layout_row="7"
|
||||
android:text="Merge old activity data" />
|
||||
|
||||
|
||||
</GridLayout>
|
||||
</ScrollView>
|
||||
|
||||
|
@ -1 +1 @@
|
||||
include ':app'
|
||||
include ':app', ':GBDaoGenerator'
|
||||
|
Loading…
Reference in New Issue
Block a user