diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java index 99b28e06..f7b942b6 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java @@ -60,7 +60,7 @@ public class GBApplication extends Application { private static LimitedQueue mIDSenderLookup = new LimitedQueue(16); private static Prefs prefs; private static GBPrefs gbPrefs; - private static DBHandler lockHandler; + private static LockHandler lockHandler; /** * Note: is null on Lollipop and Kitkat */ @@ -156,7 +156,10 @@ public class GBApplication extends Application { DBOpenHelper helper = new DBOpenHelper(context, "test-db", null); SQLiteDatabase db = helper.getWritableDatabase(); DaoMaster daoMaster = new DaoMaster(db); - lockHandler = new LockHandler(daoMaster, helper); + if (lockHandler == null) { + lockHandler = new LockHandler(); + } + lockHandler.init(daoMaster, helper); } public static Context getContext() { @@ -179,6 +182,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() diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/LockHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/LockHandler.java index 70fb5b63..69317def 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/LockHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/LockHandler.java @@ -9,23 +9,48 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; /** - * A dummy DBHandler that does nothing more than implementing the release() method. - * It is solely used for locking concurrent access to the database session. + * Provides lowlevel access to the database. */ public class LockHandler implements DBHandler { - private final DaoMaster daoMaster; - private DaoSession session; - private final SQLiteOpenHelper helper; + private DaoMaster daoMaster = null; + private DaoSession session = null; + private SQLiteOpenHelper helper = null; - public LockHandler(DaoMaster daoMaster, DBOpenHelper helper) { + 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"); + } + } + + 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(); } @@ -34,7 +59,7 @@ public class LockHandler implements DBHandler { if (session != null) { throw new IllegalStateException("session must be null"); } - // this will create completely new db instances. This handler will be dead + // this will create completely new db instances and in turn update this handler through #init() GBApplication.setupDatabase(GBApplication.getContext()); } @@ -46,20 +71,25 @@ public class LockHandler implements DBHandler { 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(); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java index 3fd59fd1..2de13236 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java @@ -4,20 +4,29 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +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(); 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 close() throws Exception; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java index ef9ebe30..fc5bbea4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java @@ -31,6 +31,13 @@ import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; +/** + * 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; @@ -134,9 +141,10 @@ public class DBHelper { } public static User getUser(DaoSession session) { - UserDao userDao = session.getUserDao(); - List users = userDao.loadAll(); ActivityUser prefsUser = new ActivityUser(); + UserDao userDao = session.getUserDao(); + Query query = userDao.queryBuilder().where(UserDao.Properties.Name.eq(prefsUser.getName())).build(); + List users = query.list(); User user; if (users.isEmpty()) { user = createUser(prefsUser, session); @@ -155,8 +163,6 @@ public class DBHelper { user.setGender(prefsUser.getGender()); session.getUserDao().insert(user); - ensureUserAttributes(user, prefsUser, session); - return user; } @@ -187,6 +193,7 @@ public class DBHelper { return false; } + // TODO: move this into db queries? private static boolean isValidNow(ValidByDate element) { Calendar cal = DateTimeUtils.getCalendarUTC(); Date nowUTC = cal.getTime(); @@ -221,15 +228,39 @@ public class DBHelper { 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 query = deviceDao.queryBuilder().where(DeviceDao.Properties.Identifier.eq(gbDevice.getAddress())).build(); List devices = query.list(); + Device device; if (devices.isEmpty()) { - Device device = createDevice(session, gbDevice); - return device; + device = createDevice(session, gbDevice); + } else { + device = devices.get(0); } - return devices.get(0); + ensureDeviceAttributes(device, gbDevice, session); + + return device; } private static Device createDevice(DaoSession session, GBDevice gbDevice) { @@ -239,18 +270,36 @@ public class DBHelper { DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(gbDevice); device.setManufacturer(coordinator.getManufacturer()); session.getDeviceDao().insert(device); - List deviceAttributes = device.getDeviceAttributesList(); + return device; + } + + private static void ensureDeviceAttributes(Device device, GBDevice gbDevice, DaoSession session) { + List deviceAttributes = device.getDeviceAttributesList(); + if (hasUpToDateDeviceAttributes(deviceAttributes, gbDevice)) { + return; + } DeviceAttributes attributes = new DeviceAttributes(); + attributes.setDeviceId(device.getId()); attributes.setValidFromUTC(DateTimeUtils.todayUTC()); attributes.setFirmwareVersion1(gbDevice.getFirmwareVersion()); - // TODO: firmware version2? generically or through DeviceCoordinator? + attributes.setFirmwareVersion2(gbDevice.getFirmwareVersion2()); DeviceAttributesDao attributesDao = session.getDeviceAttributesDao(); attributesDao.insert(attributes); deviceAttributes.add(attributes); + } - return device; + private static boolean hasUpToDateDeviceAttributes(List deviceAttributes, GBDevice gbDevice) { + for (DeviceAttributes attr : deviceAttributes) { + if (!isValidNow(attr)) { + return false; + } + if (isEqual(attr, gbDevice)) { + return true; + } + } + return false; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java index 127df254..26f14a41 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/AbstractSampleProvider.java @@ -10,6 +10,11 @@ import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; import nodomain.freeyourgadget.gadgetbridge.entities.MiBandActivitySampleDao; 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 + */ public abstract class AbstractSampleProvider implements SampleProvider { private static final WhereCondition[] NO_CONDITIONS = new WhereCondition[0]; private final DaoSession mSession; @@ -51,12 +56,12 @@ public abstract class AbstractSampleProvider i @Override public void addGBActivitySample(T activitySample) { - getSampleDao().insert(activitySample); + getSampleDao().insertOrReplace(activitySample); } @Override public void addGBActivitySamples(T[] activitySamples) { - getSampleDao().insertInTx(activitySamples); + getSampleDao().insertOrReplaceInTx(activitySamples); } // @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java index 837d6b74..e4ab7360 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java @@ -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; }