Merge branch 'master' into db-refactoring

master
cpfeiffer 2016-04-14 23:28:00 +02:00
commit a8279faa5b
50 changed files with 552 additions and 215 deletions

View File

@ -8,7 +8,7 @@ tasks.withType(Test) { systemProperty 'MiFirmwareDir', System.getProperty('MiFir
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "nodomain.freeyourgadget.gadgetbridge"
@ -46,12 +46,12 @@ dependencies {
testCompile "org.mockito:mockito-core:1.9.5"
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:23.1.1'
compile 'com.android.support:support-v4:23.1.1'
compile 'com.android.support:appcompat-v7:23.3.0'
compile 'com.android.support:support-v4:23.3.0'
compile 'com.android.support:design:23.3.0'
compile 'com.github.tony19:logback-android-classic:1.1.1-4'
compile 'org.slf4j:slf4j-api:1.7.7'
compile 'com.github.PhilJay:MPAndroidChart:v2.2.3'
compile 'com.github.PhilJay:MPAndroidChart:v2.2.4'
compile 'com.github.pfichtner:durationformatter:0.1.1'
compile 'de.cketti.library.changelog:ckchangelog:1.2.2'
compile 'de.greenrobot:greendao:2.1.0'

View File

@ -196,10 +196,6 @@ public class GBApplication extends Application {
dbLock.unlock();
}
public static boolean isRunningOnKitkatOrLater() {
return VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
}
public static boolean isRunningLollipopOrLater() {
return VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
}
@ -299,10 +295,14 @@ public class GBApplication extends Application {
editor.putString(PREFS_VERSION, Integer.toString(CURRENT_PREFS_VERSION));
break;
}
editor.commit();
editor.apply();
}
public static LimitedQueue getIDSenderLookup() {
return mIDSenderLookup;
}
public static boolean isDarkThemeEnabled() {
return sharedPrefs.getString("pref_key_theme", context.getString(R.string.pref_theme_value_light)).equals(context.getString(R.string.pref_theme_value_dark));
}
}

View File

@ -1,7 +1,6 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
@ -18,7 +17,7 @@ import android.support.v4.app.FragmentPagerAdapter;
*
* @see AbstractGBFragment
*/
public abstract class AbstractGBFragmentActivity extends FragmentActivity {
public abstract class AbstractGBFragmentActivity extends GBActivity {
/**
* The {@link android.support.v4.view.PagerAdapter} that will provide
* fragments for each of the sections. We use a

View File

@ -20,6 +20,9 @@ import android.view.ViewGroup;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
/**
* A settings activity with support for preferences directly displaying their value.
* If you combine such preferences with a custom OnPreferenceChangeListener, you have
@ -86,6 +89,11 @@ public abstract class AbstractSettingsActivity extends PreferenceActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
if (GBApplication.isDarkThemeEnabled()) {
setTheme(R.style.GadgetbridgeThemeDark);
} else {
setTheme(R.style.GadgetbridgeTheme);
}
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);

View File

@ -2,7 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v7.app.AppCompatActivity;
import android.text.format.DateFormat;
import android.view.MenuItem;
import android.widget.CheckBox;
@ -12,7 +11,7 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
public class AlarmDetails extends AppCompatActivity {
public class AlarmDetails extends GBActivity {
private GBAlarm alarm;
private TimePicker timePicker;

View File

@ -1,11 +1,10 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import nodomain.freeyourgadget.gadgetbridge.R;
public class AndroidPairingActivity extends AppCompatActivity {
public class AndroidPairingActivity extends GBActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {

View File

@ -11,7 +11,6 @@ import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.NavUtils;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
@ -28,13 +27,14 @@ import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
public class AppBlacklistActivity extends AppCompatActivity {
public class AppBlacklistActivity extends GBActivity {
private static final Logger LOG = LoggerFactory.getLogger(AppBlacklistActivity.class);
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@ -60,6 +60,29 @@ public class AppBlacklistActivity extends AppCompatActivity {
final List<ApplicationInfo> packageList = pm.getInstalledApplications(PackageManager.GET_META_DATA);
ListView appListView = (ListView) findViewById(R.id.appListView);
// sort the package list by label and blacklist status
final IdentityHashMap<ApplicationInfo, String> nameMap = new IdentityHashMap<>(packageList.size());
for (ApplicationInfo ai : packageList) {
CharSequence name = pm.getApplicationLabel(ai);
if (name == null) {
name = ai.packageName;
}
if (GBApplication.blacklist.contains(ai.packageName)) {
// sort blacklisted first by prefixing with a '!'
name = "!" + name;
}
nameMap.put(ai, name.toString());
}
Collections.sort(packageList, new Comparator<ApplicationInfo>() {
@Override
public int compare(ApplicationInfo ai1, ApplicationInfo ai2) {
final String s1 = nameMap.get(ai1);
final String s2 = nameMap.get(ai2);
return s1.compareTo(s2);
}
});
final ArrayAdapter<ApplicationInfo> adapter = new ArrayAdapter<ApplicationInfo>(this, R.layout.item_with_checkbox, packageList) {
@Override
public View getView(int position, View view, ViewGroup parent) {
@ -80,22 +103,6 @@ public class AppBlacklistActivity extends AppCompatActivity {
checkbox.setChecked(GBApplication.blacklist.contains(appInfo.packageName));
Collections.sort(packageList, new Comparator<ApplicationInfo>() {
@Override
public int compare(ApplicationInfo ai1, ApplicationInfo ai2) {
boolean blacklisted1 = GBApplication.blacklist.contains(ai1.packageName);
boolean blacklisted2 = GBApplication.blacklist.contains(ai2.packageName);
if ((blacklisted1 && blacklisted2) || (!blacklisted1 && !blacklisted2)) {
// both blacklisted or both not blacklisted = sort by alphabet
return ai1.packageName.compareTo(ai2.packageName);
} else if (blacklisted1) {
return -1;
} else {
return 1;
}
}
});
return view;
}
};

View File

@ -10,7 +10,6 @@ import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.NavUtils;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.view.ContextMenu;
import android.view.MenuItem;
import android.view.View;
@ -37,7 +36,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
public class AppManagerActivity extends AppCompatActivity {
public class AppManagerActivity extends GBActivity {
public static final String ACTION_REFRESH_APPLIST
= "nodomain.freeyourgadget.gadgetbridge.appmanager.action.refresh_applist";
private static final Logger LOG = LoggerFactory.getLogger(AppManagerActivity.class);

View File

@ -4,7 +4,6 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import android.widget.ListView;
@ -21,7 +20,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBAlarm;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_ALARMS;
public class ConfigureAlarms extends AppCompatActivity {
public class ConfigureAlarms extends GBActivity {
private static final int REQ_CONFIGURE_ALARM = 1;

View File

@ -20,7 +20,6 @@ import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.AppCompatActivity;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
@ -48,7 +47,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class ControlCenter extends AppCompatActivity {
public class ControlCenter extends GBActivity {
private static final Logger LOG = LoggerFactory.getLogger(ControlCenter.class);

View File

@ -13,7 +13,6 @@ import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.RemoteInput;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
@ -37,7 +36,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class DebugActivity extends AppCompatActivity {
public class DebugActivity extends GBActivity {
private static final Logger LOG = LoggerFactory.getLogger(DebugActivity.class);
private static final String EXTRA_REPLY = "reply";

View File

@ -12,7 +12,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Parcelable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
@ -33,7 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class DiscoveryActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {
public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemClickListener {
private static final Logger LOG = LoggerFactory.getLogger(DiscoveryActivity.class);
private static final long SCAN_DURATION = 60000; // 60s

View File

@ -4,7 +4,6 @@ import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.MenuItem;
import android.webkit.ConsoleMessage;
@ -32,7 +31,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.PebbleUtils;
public class ExternalPebbleJSActivity extends AppCompatActivity {
public class ExternalPebbleJSActivity extends GBActivity {
private static final Logger LOG = LoggerFactory.getLogger(ExternalPebbleJSActivity.class);

View File

@ -8,7 +8,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
@ -35,7 +34,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class FwAppInstallerActivity extends AppCompatActivity implements InstallActivity {
public class FwAppInstallerActivity extends GBActivity implements InstallActivity {
private static final Logger LOG = LoggerFactory.getLogger(FwAppInstallerActivity.class);
private static final String ITEM_DETAILS = "details";

View File

@ -0,0 +1,22 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
public class GBActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
if (GBApplication.isDarkThemeEnabled()) {
setTheme(R.style.GadgetbridgeThemeDark);
} else {
setTheme(R.style.GadgetbridgeTheme);
}
super.onCreate(savedInstanceState);
}
}

View File

@ -0,0 +1,14 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
public class HeartRateUtils {
public static final int MAX_HEART_RATE_VALUE = 250;
public static final int MIN_HEART_RATE_VALUE = 0;
/**
* The maxiumum gap between two hr measurements in which
* we interpolate between the measurements. Otherwise, two
* distinct measurements will be shown.
*
* Value is in minutes
*/
public static final int MAX_HR_MEASUREMENTS_GAP_MINUTES = 10;
}

View File

@ -133,11 +133,6 @@ public class SettingsActivity extends AbstractSettingsActivity {
@Override
protected String[] getPreferenceKeysWithSummary() {
return new String[]{
"audio_player",
"notification_mode_calls",
"notification_mode_sms",
"notification_mode_k9mail",
"pebble_activitytracker",
"pebble_emu_addr",
"pebble_emu_port",
"pebble_reconnect_attempts",
@ -159,7 +154,6 @@ public class SettingsActivity extends AbstractSettingsActivity {
"canned_reply_15",
"canned_reply_16",
PREF_USER_YEAR_OF_BIRTH,
PREF_USER_GENDER,
PREF_USER_HEIGHT_CM,
PREF_USER_WEIGHT_KG,
PREF_USER_SLEEP_DURATION,

View File

@ -4,11 +4,13 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.support.v4.content.LocalBroadcastManager;
import android.util.TypedValue;
import android.view.View;
import com.github.mikephil.charting.charts.BarLineChartBase;
@ -39,6 +41,7 @@ import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBFragment;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
@ -116,6 +119,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
protected int CHART_TEXT_COLOR;
protected int LEGEND_TEXT_COLOR;
protected int HEARTRATE_COLOR;
protected int HEARTRATE_FILL_COLOR;
protected int AK_ACTIVITY_COLOR;
protected int AK_DEEP_SLEEP_COLOR;
protected int AK_LIGHT_SLEEP_COLOR;
@ -147,11 +151,16 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
}
protected void init() {
BACKGROUND_COLOR = getResources().getColor(R.color.background_material_light);
DESCRIPTION_COLOR = getResources().getColor(R.color.primarytext);
TypedValue typedValue = new TypedValue();
Resources.Theme theme = getContext().getTheme();
theme.resolveAttribute(android.R.attr.background, typedValue, true);
BACKGROUND_COLOR = typedValue.data;
theme.resolveAttribute(android.R.attr.textColor, typedValue, true);
LEGEND_TEXT_COLOR = DESCRIPTION_COLOR = typedValue.data;
CHART_TEXT_COLOR = getResources().getColor(R.color.secondarytext);
LEGEND_TEXT_COLOR = getResources().getColor(R.color.primarytext);
HEARTRATE_COLOR = getResources().getColor(R.color.chart_heartrate);
HEARTRATE_FILL_COLOR = getResources().getColor(R.color.chart_heartrate_fill);
AK_ACTIVITY_COLOR = getResources().getColor(R.color.chart_activity_light);
AK_DEEP_SLEEP_COLOR = getResources().getColor(R.color.chart_light_sleep_light);
AK_LIGHT_SLEEP_COLOR = getResources().getColor(R.color.chart_deep_sleep_light);
@ -335,6 +344,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
}
protected void configureChartDefaults(Chart<?> chart) {
chart.setDescription("");
// if enabled, the chart will always start at zero on the y-axis
chart.setNoDataText(getString(R.string.chart_no_data_synchronize));
@ -343,6 +354,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
// enable touch gestures
chart.setTouchEnabled(true);
setupLegend(chart);
}
protected void configureBarLineChartDefaults(BarLineChartBase<?> chart) {
@ -380,9 +393,9 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
/**
* This method reads the data from the database, analyzes and prepares it for
* the charts. This will be called from a background task, so there must not be
* any UI access. #renderCharts will be automatically called after this method.
* any UI access. #updateChartsInUIThread and #renderCharts will be automatically called after this method.
*/
protected abstract void refreshInBackground(DBHandler db, GBDevice device);
protected abstract ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device);
/**
* Triggers the actual (re-) rendering of the chart.
@ -390,7 +403,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
*/
protected abstract void renderCharts();
protected void refresh(GBDevice gbDevice, BarLineChartBase chart, List<ActivitySample> samples) {
protected DefaultChartsData refresh(GBDevice gbDevice, List<ActivitySample> samples) {
Calendar cal = GregorianCalendar.getInstance();
cal.clear();
Date date;
@ -398,6 +411,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
String dateStringTo = "";
LOG.info("" + getTitle() + ": number of samples:" + samples.size());
CombinedData combinedData;
if (samples.size() > 1) {
boolean annotate = true;
boolean use_steps_as_movement;
@ -413,6 +427,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
boolean hr = supportsHeartrate();
List<Entry> heartrateEntries = hr ? new ArrayList<Entry>(numEntries) : null;
List<Integer> colors = new ArrayList<>(numEntries); // this is kinda inefficient...
int lastHrSampleIndex = -1;
for (int i = 0; i < numEntries; i++) {
ActivitySample sample = samples.get(i);
@ -455,7 +470,13 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
}
activityEntries.add(createBarEntry(value, i));
if (hr && isValidHeartRateValue(sample.getCustomValue())) {
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));
lastHrSampleIndex = i;
}
String xLabel = "";
@ -486,11 +507,11 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
xLabels.add(xLabel);
}
chart.getXAxis().setValues(xLabels);
// chart.getXAxis().setValues(xLabels);
BarDataSet activitySet = createActivitySet(activityEntries, colors, "Activity");
// create a data object with the datasets
CombinedData combinedData = new CombinedData(xLabels);
combinedData = new CombinedData(xLabels);
List<IBarDataSet> list = new ArrayList<>();
list.add(activitySet);
BarData barData = new BarData(xLabels, list);
@ -503,21 +524,17 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
combinedData.setData(lineData);
}
chart.setDescription("");
// chart.setDescription(getString(R.string.sleep_activity_date_range, dateStringFrom, dateStringTo));
// chart.setDescriptionPosition(?, ?);
setupLegend(chart);
chart.setData(combinedData);
} else {
CombinedData data = new CombinedData(Collections.<String>emptyList());
chart.setData(data);
combinedData = new CombinedData(Collections.<String>emptyList());
}
return new DefaultChartsData(combinedData);
}
protected boolean isValidHeartRateValue(int value) {
return value > 0 && value < 255;
return value > HeartRateUtils.MIN_HEART_RATE_VALUE && value < HeartRateUtils.MAX_HEART_RATE_VALUE;
}
/**
@ -561,23 +578,19 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
protected LineDataSet createHeartrateSet(List<Entry> values, String label) {
LineDataSet set1 = new LineDataSet(values, label);
set1.setLineWidth(0.8f);
set1.setColor(HEARTRATE_COLOR);
// set1.setColors(colors);
set1.setDrawCubic(true);
set1.setCubicIntensity(0.1f);
// //set1.setDrawFilled(true);
// set1.setDrawCircles(false);
// set1.setLineWidth(2f);
set1.setDrawCircles(false);
// set1.setCircleRadius(2f);
// set1.setDrawFilled(true);
set1.setLineWidth(0.8f);
// set1.setColor(getResources().getColor(android.R.color.background_light));
// set1.setCircleColor(HEARTRATE_COLOR);
// set1.setFillColor(ColorTemplate.getHoloBlue());
set1.setDrawValues(true);
// set1.setHighLightColor(Color.rgb(128, 0, 255));
// set1.setColor(Color.rgb(89, 178, 44));
set1.setDrawValues(true);
set1.setValueTextColor(CHART_TEXT_COLOR);
set1.setAxisDependency(YAxis.AxisDependency.RIGHT);
return set1;
@ -622,6 +635,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
}
public class RefreshTask extends DBAccess {
private ChartsData chartsData;
public RefreshTask(String task, Context context) {
super(task, context);
}
@ -630,7 +645,9 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
protected void doInBackground(DBHandler db) {
ChartsHost chartsHost = getChartsHost();
if (chartsHost != null) {
refreshInBackground(db, chartsHost.getDevice());
chartsData = refreshInBackground(chartsHost, db, chartsHost.getDevice());
} else {
cancel(true);
}
}
@ -639,6 +656,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
super.onPostExecute(o);
FragmentActivity activity = getActivity();
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
updateChartsnUIThread(chartsData);
renderCharts();
} else {
LOG.info("Not rendering charts because activity is not available anymore");
@ -646,6 +664,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
}
}
protected abstract void updateChartsnUIThread(ChartsData chartsData);
/**
* Returns true if the date was successfully shifted, and false if the shift
* was ignored, e.g. when the to-value is in the future.
@ -689,4 +709,16 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
private int toTimestamp(Date date) {
return (int) ((date.getTime() / 1000));
}
public static class DefaultChartsData extends ChartsData {
private final CombinedData combinedData;
public DefaultChartsData(CombinedData combinedData) {
this.combinedData = combinedData;
}
public CombinedData getCombinedData() {
return combinedData;
}
}
}

View File

@ -20,6 +20,7 @@ import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
@ -83,8 +84,8 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(true);
yAxisRight.setTextColor(CHART_TEXT_COLOR);
yAxisRight.setAxisMaxValue(250);
yAxisRight.setAxisMinValue(0);
yAxisRight.setAxisMaxValue(HeartRateUtils.MAX_HEART_RATE_VALUE);
yAxisRight.setAxisMinValue(HeartRateUtils.MIN_HEART_RATE_VALUE);
// refresh immediately instead of use refreshIfVisible(), for perceived performance
refresh();
@ -106,11 +107,16 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
}
@Override
protected void refreshInBackground(DBHandler db, GBDevice device) {
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
List<ActivitySample> samples = getSamples(db, device);
refresh(device, mChart, samples);
return refresh(device, samples);
}
@Override
protected void updateChartsnUIThread(ChartsData chartsData) {
DefaultChartsData dcd = (DefaultChartsData) chartsData;
mChart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
mChart.setData(dcd.getCombinedData());
}
protected void renderCharts() {

View File

@ -0,0 +1,4 @@
package nodomain.freeyourgadget.gadgetbridge.activities.charts;
public abstract class ChartsData {
}

View File

@ -23,6 +23,7 @@ import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.data.ChartData;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
@ -39,10 +40,12 @@ import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.Measurement;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class LiveActivityFragment extends AbstractChartFragment {
@ -63,6 +66,9 @@ public class LiveActivityFragment extends AbstractChartFragment {
private final Steps mSteps = new Steps();
private ScheduledExecutorService pulseScheduler;
private int maxStepsResetCounter;
private List<Measurement> heartRateValues;
private LineDataSet mHeartRateSet;
private int mHeartRate;
private class Steps {
private int initialSteps;
@ -145,16 +151,36 @@ public class LiveActivityFragment extends AbstractChartFragment {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
case DeviceService.ACTION_REALTIME_STEPS:
case DeviceService.ACTION_REALTIME_STEPS: {
int steps = intent.getIntExtra(DeviceService.EXTRA_REALTIME_STEPS, 0);
long timestamp = intent.getLongExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
refreshCurrentSteps(steps, timestamp);
addEntries(steps, timestamp);
break;
}
case DeviceService.ACTION_HEARTRATE_MEASUREMENT: {
int heartRate = intent.getIntExtra(DeviceService.EXTRA_HEART_RATE_VALUE, 0);
long timestamp = intent.getLongExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
if (isValidHeartRateValue(heartRate)) {
setCurrentHeartRate(heartRate, timestamp);
}
break;
}
}
}
};
private void refreshCurrentSteps(int steps, long timestamp) {
private void setCurrentHeartRate(int heartRate, long timestamp) {
addHistoryDataSet(true);
mHeartRate = heartRate;
}
private int getCurrentHeartRate() {
int result = mHeartRate;
mHeartRate = -1;
return result;
}
private void addEntries(int steps, long timestamp) {
mSteps.updateCurrentSteps(steps, timestamp);
if (++maxStepsResetCounter > RESET_COUNT) {
maxStepsResetCounter = 0;
@ -163,10 +189,10 @@ public class LiveActivityFragment extends AbstractChartFragment {
// Or: count down the steps until goal reached? And then flash GOAL REACHED -> Set stretch goal
LOG.info("Steps: " + steps + ", total: " + mSteps.getTotalSteps() + ", current: " + mSteps.getStepsPerMinute(false));
// refreshCurrentSteps();
// addEntries();
}
private void refreshCurrentSteps() {
private void addEntries() {
mTotalStepsChart.setSingleEntryYValue(mSteps.getTotalSteps());
YAxis stepsPerMinuteCurrentYAxis = mStepsPerMinuteCurrentChart.getAxisLeft();
int maxStepsPerMinute = mSteps.getMaxStepsPerMinute();
@ -180,24 +206,36 @@ public class LiveActivityFragment extends AbstractChartFragment {
int stepsPerMinute = mSteps.getStepsPerMinute(true);
mStepsPerMinuteCurrentChart.setSingleEntryYValue(stepsPerMinute);
if (mStepsPerMinuteHistoryChart.getData() == null) {
if (mSteps.getTotalSteps() == 0) {
return; // ignore the first default value to keep the "no-data-description" visible
}
LineData data = new LineData();
mStepsPerMinuteHistoryChart.setData(data);
data.addDataSet(mHistorySet);
if (!addHistoryDataSet(false)) {
return;
}
LineData historyData = (LineData) mStepsPerMinuteHistoryChart.getData();
historyData.addXValue("");
historyData.addEntry(new Entry(stepsPerMinute, mHistorySet.getEntryCount()), 0);
ChartData data = mStepsPerMinuteHistoryChart.getData();
data.addXValue("");
if (stepsPerMinute < 0) {
stepsPerMinute = 0;
}
mHistorySet.addEntry(new Entry(stepsPerMinute, data.getXValCount() - 1));
int hr = getCurrentHeartRate();
if (hr < 0) {
hr = 0;
}
mHeartRateSet.addEntry(new Entry(hr, data.getXValCount() - 1));
}
mTotalStepsData.notifyDataSetChanged();
mStepsPerMinuteData.notifyDataSetChanged();
mStepsPerMinuteHistoryChart.notifyDataSetChanged();
renderCharts();
private boolean addHistoryDataSet(boolean force) {
if (mStepsPerMinuteHistoryChart.getData() == null) {
// ignore the first default value to keep the "no-data-description" visible
if (force || mSteps.getTotalSteps() > 0) {
LineData data = new LineData();
data.addDataSet(mHistorySet);
data.addDataSet(mHeartRateSet);
mStepsPerMinuteHistoryChart.setData(data);
return true;
}
return false;
}
return true;
}
@Nullable
@ -205,6 +243,8 @@ public class LiveActivityFragment extends AbstractChartFragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
IntentFilter filterLocal = new IntentFilter();
filterLocal.addAction(DeviceService.ACTION_REALTIME_STEPS);
filterLocal.addAction(DeviceService.ACTION_HEARTRATE_MEASUREMENT);
heartRateValues = new ArrayList<>();
View rootView = inflater.inflate(R.layout.fragment_live_activity, container, false);
@ -227,16 +267,12 @@ public class LiveActivityFragment extends AbstractChartFragment {
@Override
public void onPause() {
super.onPause();
if (pulseScheduler != null) {
pulseScheduler.shutdownNow();
pulseScheduler = null;
}
stopActivityPulse();
}
@Override
public void onResume() {
super.onResume();
pulseScheduler = startActivityPulse();
}
private ScheduledExecutorService startActivityPulse() {
@ -258,11 +294,33 @@ public class LiveActivityFragment extends AbstractChartFragment {
return service;
}
private void stopActivityPulse() {
if (pulseScheduler != null) {
pulseScheduler.shutdownNow();
pulseScheduler = null;
}
}
/**
* Called in the UI thread.
*/
private void pulse() {
refreshCurrentSteps();
addEntries();
LineData historyData = (LineData) mStepsPerMinuteHistoryChart.getData();
if (historyData == null) {
return;
}
historyData.notifyDataChanged();
mTotalStepsData.notifyDataSetChanged();
mStepsPerMinuteData.notifyDataSetChanged();
mStepsPerMinuteHistoryChart.notifyDataSetChanged();
renderCharts();
// have to enable it again and again to keep it measureing
GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(true);
}
private long getPulseIntervalMillis() {
@ -272,15 +330,19 @@ public class LiveActivityFragment extends AbstractChartFragment {
@Override
protected void onMadeVisibleInActivity() {
GBApplication.deviceService().onEnableRealtimeSteps(true);
GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(true);
super.onMadeVisibleInActivity();
if (getActivity() != null) {
getActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
pulseScheduler = startActivityPulse();
}
@Override
protected void onMadeInvisibleInActivity() {
stopActivityPulse();
GBApplication.deviceService().onEnableRealtimeSteps(false);
GBApplication.deviceService().onEnableRealtimeHeartRateMeasurement(false);
if (getActivity() != null) {
getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
@ -346,6 +408,7 @@ public class LiveActivityFragment extends AbstractChartFragment {
private void setupHistoryChart(BarLineChartBase chart) {
configureBarLineChartDefaults(chart);
chart.setTouchEnabled(false); // no zooming or anything, because it's updated all the time
chart.setBackgroundColor(BACKGROUND_COLOR);
chart.setDescriptionColor(DESCRIPTION_COLOR);
chart.setDescription(getString(R.string.live_activity_steps_per_minute_history));
@ -367,22 +430,28 @@ public class LiveActivityFragment extends AbstractChartFragment {
y.setDrawGridLines(false);
y.setDrawTopYLabelEntry(false);
y.setTextColor(CHART_TEXT_COLOR);
y.setEnabled(true);
y.setAxisMinValue(0);
YAxis yAxisRight = chart.getAxisRight();
yAxisRight.setDrawGridLines(false);
yAxisRight.setEnabled(false);
yAxisRight.setDrawLabels(false);
yAxisRight.setEnabled(true);
yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(false);
yAxisRight.setTextColor(CHART_TEXT_COLOR);
yAxisRight.setAxisMaxValue(HeartRateUtils.MAX_HEART_RATE_VALUE);
yAxisRight.setAxisMinValue(HeartRateUtils.MIN_HEART_RATE_VALUE);
mHistorySet = new LineDataSet(new ArrayList<Entry>(), getString(R.string.live_activity_steps_history));
mHistorySet.setAxisDependency(YAxis.AxisDependency.LEFT);
mHistorySet.setColor(akActivity.color);
mHistorySet.setDrawCircles(false);
mHistorySet.setDrawCubic(true);
mHistorySet.setDrawFilled(true);
mHistorySet.setDrawValues(false);
mHeartRateSet = createHeartrateSet(new ArrayList<Entry>(), getString(R.string.live_activity_heart_rate));
mHeartRateSet.setDrawValues(false);
}
@Override
@ -402,7 +471,13 @@ public class LiveActivityFragment extends AbstractChartFragment {
}
@Override
protected void refreshInBackground(DBHandler db, GBDevice device) {
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
throw new UnsupportedOperationException();
}
@Override
protected void updateChartsnUIThread(ChartsData chartsData) {
throw new UnsupportedOperationException();
}
@Override

View File

@ -27,6 +27,7 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.HeartRateUtils;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount;
@ -48,14 +49,16 @@ public class SleepChartFragment extends AbstractChartFragment {
private int mSmartAlarmGoneOff = -1;
@Override
protected void refreshInBackground(DBHandler db, GBDevice device) {
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
List<ActivitySample> samples = getSamples(db, device);
refresh(device, mActivityChart, samples);
refreshSleepAmounts(device, mSleepAmountChart, samples);
MySleepChartsData mySleepChartsData = refreshSleepAmounts(device, samples);
DefaultChartsData chartsData = refresh(device, samples);
return new MyChartsData(mySleepChartsData, chartsData);
}
private void refreshSleepAmounts(GBDevice mGBDevice, PieChart pieChart, List<ActivitySample> samples) {
private MySleepChartsData refreshSleepAmounts(GBDevice mGBDevice, List<ActivitySample> samples) {
ActivityAnalysis analysis = new ActivityAnalysis();
ActivityAmounts amounts = analysis.calculateActivityAmounts(samples);
PieData data = new PieData();
@ -73,7 +76,6 @@ public class SleepChartFragment extends AbstractChartFragment {
}
}
String totalSleep = DateTimeUtils.formatDurationHoursMinutes(totalSeconds, TimeUnit.SECONDS);
pieChart.setCenterText(totalSleep);
PieDataSet set = new PieDataSet(entries, "");
set.setValueFormatter(new ValueFormatter() {
@Override
@ -83,10 +85,18 @@ public class SleepChartFragment extends AbstractChartFragment {
});
set.setColors(colors);
data.setDataSet(set);
pieChart.setData(data);
pieChart.getLegend().setEnabled(false);
//setupLegend(pieChart);
return new MySleepChartsData(totalSleep, data);
}
@Override
protected void updateChartsnUIThread(ChartsData chartsData) {
MyChartsData mcd = (MyChartsData) chartsData;
mSleepAmountChart.setCenterText(mcd.getPieData().getTotalSleep());
mSleepAmountChart.setData(mcd.getPieData().getPieData());
mActivityChart.setData(mcd.getChartsData().getCombinedData());
}
@Override
@ -132,6 +142,7 @@ public class SleepChartFragment extends AbstractChartFragment {
mSleepAmountChart.setDescription("");
mSleepAmountChart.setNoDataTextDescription("");
mSleepAmountChart.setNoDataText("");
mSleepAmountChart.getLegend().setEnabled(false);
}
private void setupActivityChart() {
@ -164,8 +175,8 @@ public class SleepChartFragment extends AbstractChartFragment {
yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(true);
yAxisRight.setTextColor(CHART_TEXT_COLOR);
yAxisRight.setAxisMaxValue(250);
yAxisRight.setAxisMinValue(0);
yAxisRight.setAxisMaxValue(HeartRateUtils.MAX_HEART_RATE_VALUE);
yAxisRight.setAxisMinValue(HeartRateUtils.MIN_HEART_RATE_VALUE);
}
protected void setupLegend(Chart chart) {
@ -194,4 +205,40 @@ public class SleepChartFragment extends AbstractChartFragment {
mActivityChart.animateX(ANIM_TIME, Easing.EasingOption.EaseInOutQuart);
mSleepAmountChart.invalidate();
}
private static class MySleepChartsData extends ChartsData {
private String totalSleep;
private final PieData pieData;
public MySleepChartsData(String totalSleep, PieData pieData) {
this.totalSleep = totalSleep;
this.pieData = pieData;
}
public PieData getPieData() {
return pieData;
}
public CharSequence getTotalSleep() {
return totalSleep;
}
}
private static class MyChartsData extends ChartsData {
private final DefaultChartsData chartsData;
private final MySleepChartsData pieData;
public MyChartsData(MySleepChartsData pieData, DefaultChartsData chartsData) {
this.pieData = pieData;
this.chartsData = chartsData;
}
public MySleepChartsData getPieData() {
return pieData;
}
public DefaultChartsData getChartsData() {
return chartsData;
}
}
}

View File

@ -42,19 +42,30 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
private Locale mLocale;
private int mTargetSteps = 10000;
private CombinedChart mWeekStepsChart;
private PieChart mTodayStepsChart;
private CombinedChart mWeekStepsChart;
@Override
protected void refreshInBackground(DBHandler db, GBDevice device) {
ChartsHost chartsHost = getChartsHost();
if (chartsHost != null) {
Calendar day = Calendar.getInstance();
day.setTime(chartsHost.getEndDate());
//NB: we could have omitted the day, but this way we can move things to the past easily
refreshDaySteps(db, mTodayStepsChart, day, device);
refreshWeekBeforeSteps(db, mWeekStepsChart, day, device);
}
protected ChartsData refreshInBackground(ChartsHost chartsHost, DBHandler db, GBDevice device) {
Calendar day = Calendar.getInstance();
day.setTime(chartsHost.getEndDate());
//NB: we could have omitted the day, but this way we can move things to the past easily
DaySteps daySteps = refreshDaySteps(db, day, device);
DefaultChartsData weekBeforeStepsData = refreshWeekBeforeSteps(db, mWeekStepsChart, day, device);
return new MyChartsData(daySteps, weekBeforeStepsData);
}
@Override
protected void updateChartsnUIThread(ChartsData chartsData) {
MyChartsData mcd = (MyChartsData) chartsData;
// setupLegend(mWeekStepsChart);
mTodayStepsChart.setCenterText(NumberFormat.getNumberInstance(mLocale).format(mcd.getDaySteps().totalSteps));
mTodayStepsChart.setData(mcd.getDaySteps().data);
mWeekStepsChart.setData(mcd.getWeekBeforeStepsData().getCombinedData());
mWeekStepsChart.getLegend().setEnabled(false);
}
@Override
@ -63,7 +74,7 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
mTodayStepsChart.invalidate();
}
private void refreshWeekBeforeSteps(DBHandler db, CombinedChart combinedChart, Calendar day, GBDevice device) {
private DefaultChartsData refreshWeekBeforeSteps(DBHandler db, CombinedChart combinedChart, Calendar day, GBDevice device) {
ActivityAnalysis analysis = new ActivityAnalysis();
@ -90,18 +101,16 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
CombinedData combinedData = new CombinedData(labels);
combinedData.setData(barData);
setupLegend(combinedChart);
combinedChart.setData(combinedData);
combinedChart.getLegend().setEnabled(false);
return new DefaultChartsData(combinedData);
}
private void refreshDaySteps(DBHandler db, PieChart pieChart, Calendar day, GBDevice device) {
private DaySteps refreshDaySteps(DBHandler db, Calendar day, GBDevice device) {
ActivityAnalysis analysis = new ActivityAnalysis();
int totalSteps = analysis.calculateTotalSteps(getSamplesOfDay(db, day, device));
pieChart.setCenterText(NumberFormat.getNumberInstance(mLocale).format(totalSteps));
PieData data = new PieData();
List<Entry> entries = new ArrayList<>();
List<Integer> colors = new ArrayList<>();
@ -123,9 +132,8 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
data.setDataSet(set);
//this hides the values (numeric) added to the set. These would be shown aside the strings set with addXValue above
data.setDrawValues(false);
pieChart.setData(data);
pieChart.getLegend().setEnabled(false);
return new DaySteps(data, totalSteps);
}
@Override
@ -164,6 +172,8 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
mTodayStepsChart.setDescription(getContext().getString(R.string.weeksteps_today_steps_description, mTargetSteps));
mTodayStepsChart.setNoDataTextDescription("");
mTodayStepsChart.setNoDataText("");
mTodayStepsChart.getLegend().setEnabled(false);
// setupLegend(mTodayStepsChart);
}
private void setupWeekStepsChart() {
@ -196,12 +206,12 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
}
protected void setupLegend(Chart chart) {
List<Integer> legendColors = new ArrayList<>(1);
List<String> legendLabels = new ArrayList<>(1);
legendColors.add(akActivity.color);
legendLabels.add(getContext().getString(R.string.chart_steps));
chart.getLegend().setCustom(legendColors, legendLabels);
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
// List<Integer> legendColors = new ArrayList<>(1);
// List<String> legendLabels = new ArrayList<>(1);
// legendColors.add(akActivity.color);
// legendLabels.add(getContext().getString(R.string.chart_steps));
// chart.getLegend().setCustom(legendColors, legendLabels);
// chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
}
private List<ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, GBDevice device) {
@ -226,4 +236,32 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
protected List<ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
return super.getAllSamples(db, device, tsFrom, tsTo);
}
private static class DaySteps {
private final PieData data;
private final int totalSteps;
public DaySteps(PieData data, int totalSteps) {
this.data = data;
this.totalSteps = totalSteps;
}
}
private static class MyChartsData extends ChartsData {
private final DefaultChartsData weekBeforeStepsData;
private final DaySteps daySteps;
public MyChartsData(DaySteps daySteps, DefaultChartsData weekBeforeStepsData) {
this.daySteps = daySteps;
this.weekBeforeStepsData = weekBeforeStepsData;
}
public DaySteps getDaySteps() {
return daySteps;
}
public DefaultChartsData getWeekBeforeStepsData() {
return weekBeforeStepsData;
}
}
}

View File

@ -44,6 +44,8 @@ public interface EventHandler {
void onHeartRateTest();
void onEnableRealtimeHeartRateMeasurement(boolean enable);
void onFindDevice(boolean start);
void onScreenshotReq();

View File

@ -59,20 +59,14 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
protected String[] getPreferenceKeysWithSummary() {
return new String[]{
PREF_USER_ALIAS,
PREF_MIBAND_WEARSIDE,
PREF_MIBAND_ADDRESS,
PREF_MIBAND_FITNESS_GOAL,
PREF_MIBAND_DONT_ACK_TRANSFER,
PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR,
getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_SMS),
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_SMS),
getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_INCOMING_CALL),
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_INCOMING_CALL),
getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_K9MAIL),
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_K9MAIL),
getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_PEBBLEMSG),
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_PEBBLEMSG),
getNotificationPrefKey(VIBRATION_PROFILE, ORIGIN_GENERIC),
getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_GENERIC),
};
}

View File

@ -216,4 +216,11 @@ public class GBDeviceService implements DeviceService {
.putExtra(EXTRA_BOOLEAN_ENABLE, enable);
invokeService(intent);
}
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
Intent intent = createIntent().setAction(ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT)
.putExtra(EXTRA_BOOLEAN_ENABLE, enable);
invokeService(intent);
}
}

View File

@ -33,7 +33,9 @@ public interface DeviceService extends EventHandler {
String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms";
String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps";
String ACTION_REALTIME_STEPS = PREFIX + ".action.realtime_steps";
String ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT = PREFIX + ".action.realtime_hr_measurement";
String ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT = PREFIX + ".action.enable_heartrate_sleep_support";
String ACTION_HEARTRATE_MEASUREMENT = PREFIX + ".action.hr_measurement";
String EXTRA_DEVICE_ADDRESS = "device_address";
String EXTRA_NOTIFICATION_BODY = "notification_body";
String EXTRA_NOTIFICATION_FLAGS = "notification_flags";
@ -62,6 +64,7 @@ public interface DeviceService extends EventHandler {
String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps";
String EXTRA_REALTIME_STEPS = "realtime_steps";
String EXTRA_TIMESTAMP = "timestamp";
String EXTRA_HEART_RATE_VALUE = "hr_value";
void start();

View File

@ -0,0 +1,33 @@
package nodomain.freeyourgadget.gadgetbridge.model;
public class Measurement {
private final int value;
private final long timestamp;
public Measurement(int value, long timestamp) {
this.value = value;
this.timestamp = timestamp;
}
public int getValue() {
return value;
}
public long getTimestamp() {
return timestamp;
}
@Override
public int hashCode() {
return (int) (71 ^ value ^ timestamp);
}
@Override
public boolean equals(Object o) {
if (o instanceof Measurement) {
Measurement m = (Measurement) o;
return timestamp == m.timestamp && value == m.value;
}
return super.equals(o);
}
}

View File

@ -46,6 +46,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CO
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETEAPP;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DISCONNECT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_REALTIME_STEPS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FETCH_ACTIVITY_DATA;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FIND_DEVICE;
@ -349,6 +350,11 @@ public class DeviceCommunicationService extends Service {
mDeviceSupport.onEnableHeartRateSleepSupport(enable);
break;
}
case ACTION_ENABLE_REALTIME_HEARTRATE_MEASUREMENT: {
boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
mDeviceSupport.onEnableRealtimeHeartRateMeasurement(enable);
break;
}
}
return START_STICKY;

View File

@ -250,4 +250,12 @@ public class ServiceDeviceSupport implements DeviceSupport {
}
delegate.onEnableHeartRateSleepSupport(enable);
}
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
if (checkBusy("enable realtime heart rate measurement: " + enable)) {
return;
}
delegate.onEnableRealtimeHeartRateMeasurement(enable);
}
}

View File

@ -603,7 +603,6 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
TransactionBuilder builder = performInitialized("HeartRateTest");
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous);
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual);
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementSleep);
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementManual);
builder.queue(getQueue());
} catch (IOException ex) {
@ -614,6 +613,24 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
}
}
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
if (supportsHeartRate()) {
try {
TransactionBuilder builder = performInitialized("EnableRealtimeHeartRateMeasurement");
if (enable) {
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementManual);
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), startHeartMeasurementContinuous);
} else {
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT), stopHeartMeasurementContinuous);
}
builder.queue(getQueue());
} catch (IOException ex) {
LOG.error("Unable to enable realtime heart rate measurement in MI1S", ex);
}
}
}
public boolean supportsHeartRate() {
return getDeviceInfo() != null && getDeviceInfo().supportsHeartrate();
}
@ -745,7 +762,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
} else if (MiBandService.UUID_CHARACTERISTIC_REALTIME_STEPS.equals(characteristicUUID)) {
handleRealtimeSteps(characteristic.getValue());
} else if (MiBandService.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristicUUID)) {
logHeartrate(characteristic.getValue());
handleHeartrate(characteristic.getValue());
} else {
LOG.info("Unhandled characteristic changed: " + characteristicUUID);
logMessageContent(characteristic.getValue());
@ -814,6 +831,15 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
}
}
private void handleHeartrate(byte[] value) {
if (value.length == 2 && value[0] == 6) {
int hrValue = (value[1] & 0xff);
Intent intent = new Intent(DeviceService.ACTION_HEARTRATE_MEASUREMENT)
.putExtra(DeviceService.EXTRA_HEART_RATE_VALUE, hrValue)
.putExtra(DeviceService.EXTRA_TIMESTAMP, System.currentTimeMillis());
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
}
}
private void handleRealtimeSteps(byte[] value) {
int steps = 0xff & value[0] | (0xff & value[1]) << 8;

View File

@ -181,4 +181,10 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
byte[] bytes = gbDeviceProtocol.encodeEnableHeartRateSleepSupport(enable);
sendToDevice(bytes);
}
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
byte[] bytes = gbDeviceProtocol.encodeEnableRealtimeHeartRateMeasurement(enable);
sendToDevice(bytes);
}
}

View File

@ -63,6 +63,8 @@ public abstract class GBDeviceProtocol {
return null;
}
public byte[] encodeEnableRealtimeHeartRateMeasurement(boolean enable) { return null; }
public GBDeviceEvent[] decodeResponse(byte[] responseData) {
return null;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 971 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1008 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 584 B

After

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 650 B

After

Width:  |  Height:  |  Size: 1021 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -1,32 +0,0 @@
<resources>
<style name="GadgetbridgeTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:colorPrimary">@color/primary_light</item>
<item name="android:colorPrimaryDark">@color/primarydark_light</item>
<item name="android:colorAccent">@color/accent</item>
<item name="android:textColor">@color/primary_text_default_material_light</item>
<item name="colorPrimary">@color/primary_light</item>
<item name="colorPrimaryDark">@color/primarydark_light</item>
<item name="colorAccent">@color/accent</item>
<!-- unfortunately it doesn't work this way :( -->
<!--
<item name="chart_deep_sleep">@color/chart_deep_sleep_light</item>
<item name="chart_light_sleep">@color/chart_light_sleep_light</item>
<item name="chart_activity">@color/chart_activity_light</item>
-->
</style>
<style name="GadgetbridgeThemeDark" parent="@android:style/Theme.Material">
<item name="android:colorPrimary">@color/primary_dark</item>
<item name="android:colorPrimaryDark">@color/primarydark_dark</item>
<item name="android:colorAccent">@color/accent</item>
<item name="android:textColor">@color/primary_text_default_material_dark</item>
<!-- unfortunately it doesn't work this way :( -->
<!--
<item name="chart_deep_sleep">@color/chart_deep_sleep_dark</item>
<item name="chart_light_sleep">@color/chart_light_sleep_dark</item>
<item name="chart_activity">@color/chart_activity_dark</item>
-->
</style>
</resources>

View File

@ -1,5 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="pref_theme_options">
<item>@string/pref_theme_light</item>
<item>@string/pref_theme_dark</item>
</string-array>
<string-array name="pref_theme_values">
<item>@string/pref_theme_value_light</item>
<item>@string/pref_theme_value_dark</item>
</string-array>
<string name="pref_theme_value_light" translatable="false">light</string>
<string name="pref_theme_value_dark" translatable="false">dark</string>
<string-array name="notification_mode">
<item>@string/always</item>
<item>@string/when_screen_off</item>

View File

@ -7,11 +7,13 @@
<color name="primarydark_dark" type="color">#f0f03000</color>
<color name="accent" type="color">#0091ea</color>
<color name="primarytext" type="color">#ff000000</color>
<color name="primarytext_light" type="color">#000000</color>
<color name="primarytext_dark" type="color">#ffffff</color>
<color name="secondarytext" type="color">#ff808080</color>
<color name="divider" type="color">#1f000000</color>
<color name="chart_heartrate" type="color">#ffab40</color>
<color name="chart_heartrate_fill" type="color">#fadab1</color>
<color name="chart_deep_sleep_light" type="color">#0071b7</color>
<color name="chart_deep_sleep_dark" type="color">#4c5aff</color>

View File

@ -39,6 +39,9 @@
<string name="pref_header_datetime">Date and Time</string>
<string name="pref_title_datetime_syctimeonconnect">Sync time</string>
<string name="pref_summary_datetime_syctimeonconnect">Sync time to device when connecting and when time or timezone changes on Android</string>
<string name="pref_title_theme">Theme</string>
<string name="pref_theme_light">Light</string>
<string name="pref_theme_dark">Dark</string>
<string name="pref_header_notifications">Notifications</string>
<string name="pref_title_notifications_repetitions">Repetitions</string>
@ -240,5 +243,6 @@
<string name="updatefirmwareoperation_update_in_progress">Firmware update in progress</string>
<string name="updatefirmwareoperation_firmware_not_sent">Firmware not sent</string>
<string name="charts_legend_heartrate">Heart Rate</string>
<string name="live_activity_heart_rate">Heart Rate</string>
</resources>

View File

@ -1,25 +1,29 @@
<resources>
<style name="GadgetbridgeTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:actionBarStyle">@style/GadgetbridgeActionBar</item>
<item name="android:actionBarWidgetTheme">@style/GadgetbridgeActionBarWidget</item>
<item name="android:textColor">@color/primarytext</item>
<item name="android:textColor">@color/primarytext_light</item>
<item name="colorPrimary">@color/primary_light</item>
<item name="colorPrimaryDark">@color/primarydark_light</item>
<item name="colorAccent">@color/accent</item>
<!-- unfortunately it doesn't work this way :( -->
<!--
<item name="chart_deep_sleep">@color/chart_deep_sleep_light</item>
<item name="chart_light_sleep">@color/chart_light_sleep_light</item>
<item name="chart_activity">@color/chart_activity_light</item>
-->
</style>
<style name="GadgetbridgeThemeDark" parent="Theme.AppCompat">
<item name="android:textColor">@color/primarytext_dark</item>
<item name="colorPrimary">@color/primary_dark</item>
<item name="colorPrimaryDark">@color/primarydark_dark</item>
<item name="colorAccent">@color/accent</item>
<style name="GadgetbridgeActionBar" parent="@android:style/Widget.Holo.Light.ActionBar.Solid.Inverse">
<item name="android:background">@color/primary_light</item>
<item name="android:backgroundStacked">@color/primarydark_light</item>
<item name="android:displayOptions">showHome|showTitle</item>
<item name="android:icon">@android:color/transparent</item>
<!-- unfortunately it doesn't work this way :( -->
<!--
<item name="chart_deep_sleep">@color/chart_deep_sleep_dark</item>
<item name="chart_light_sleep">@color/chart_light_sleep_dark</item>
<item name="chart_activity">@color/chart_activity_dark</item>
-->
</style>
<style name="GadgetbridgeActionBarWidget" parent="android:Theme.Holo.Light">
<item name="android:popupMenuStyle">@android:style/Widget.Holo.Light.PopupMenu</item>
<item name="android:dropDownListViewStyle">@android:style/Widget.Holo.Light.ListView</item>
</style>
</resources>

View File

@ -14,7 +14,8 @@
android:entries="@array/wearside"
android:entryValues="@array/wearside_values"
android:key="mi_wearside"
android:title="@string/miband_prefs_wearside" />
android:title="@string/miband_prefs_wearside"
android:summary="%s" />
<EditTextPreference
android:defaultValue="10000"
@ -50,7 +51,8 @@
android:entries="@array/vibration_profile"
android:entryValues="@array/vibration_profile_values"
android:key="mi_vibration_profile_sms"
android:title="@string/miband_prefs_vibration" />
android:title="@string/miband_prefs_vibration"
android:summary="%s" />
<EditTextPreference
android:defaultValue="3"
@ -70,7 +72,8 @@
android:entries="@array/vibration_profile"
android:entryValues="@array/vibration_profile_values"
android:key="mi_vibration_profile_incoming_call"
android:title="@string/miband_prefs_vibration" />
android:title="@string/miband_prefs_vibration"
android:summary="%s" />
<EditTextPreference
android:defaultValue="60"
@ -90,7 +93,8 @@
android:entries="@array/vibration_profile"
android:entryValues="@array/vibration_profile_values"
android:key="mi_vibration_profile_k9mail"
android:title="@string/miband_prefs_vibration" />
android:title="@string/miband_prefs_vibration"
android:summary="%s" />
<EditTextPreference
android:defaultValue="2"
@ -110,7 +114,8 @@
android:entries="@array/vibration_profile"
android:entryValues="@array/vibration_profile_values"
android:key="mi_vibration_profile_pebblemsg"
android:title="@string/miband_prefs_vibration" />
android:title="@string/miband_prefs_vibration"
android:summary="%s" />
<EditTextPreference
android:defaultValue="1"
@ -130,7 +135,8 @@
android:entries="@array/vibration_profile"
android:entryValues="@array/vibration_profile_values"
android:key="mi_vibration_profile_generic"
android:title="@string/miband_prefs_vibration" />
android:title="@string/miband_prefs_vibration"
android:summary="%s" />
<EditTextPreference
android:defaultValue="3"

View File

@ -10,7 +10,15 @@
<ListPreference
android:defaultValue="default"
android:key="audio_player"
android:title="@string/pref_title_audo_player" />
android:title="@string/pref_title_audo_player"
android:summary="%s" />
<ListPreference
android:key="pref_key_theme"
android:title="@string/pref_title_theme"
android:entries="@array/pref_theme_options"
android:entryValues="@array/pref_theme_values"
android:defaultValue="@string/pref_theme_value_light"
android:summary="%s" />
</PreferenceCategory>
<PreferenceCategory
android:key="pref_key_datetime"
@ -30,21 +38,24 @@
android:entries="@array/notification_mode_toggle"
android:entryValues="@array/notification_mode_values_toggle"
android:key="notification_mode_calls"
android:title="@string/pref_title_notifications_call" />
android:title="@string/pref_title_notifications_call"
android:summary="%s" />
<ListPreference
android:defaultValue="when_screen_off"
android:entries="@array/notification_mode"
android:entryValues="@array/notification_mode_values"
android:key="notification_mode_sms"
android:title="@string/pref_title_notifications_sms" />
android:title="@string/pref_title_notifications_sms"
android:summary="%s" />
<ListPreference
android:defaultValue="when_screen_off"
android:entries="@array/notification_mode"
android:entryValues="@array/notification_mode_values"
android:key="notification_mode_k9mail"
android:title="@string/pref_title_notifications_k9mail" />
android:title="@string/pref_title_notifications_k9mail"
android:summary="%s" />
<ListPreference
android:defaultValue="when_screen_off"
@ -139,7 +150,8 @@
android:entries="@array/gender"
android:entryValues="@array/gender_values"
android:key="activity_user_gender"
android:title="@string/activity_prefs_gender" />
android:title="@string/activity_prefs_gender"
android:summary="%s" />
<!--TODO: support localized heights and weights -->
<EditTextPreference
@ -191,7 +203,8 @@
android:entries="@array/pebble_activitytracker"
android:entryValues="@array/pebble_activitytracker_values"
android:key="pebble_activitytracker"
android:title="@string/pref_title_pebble_activitytracker" />
android:title="@string/pref_title_pebble_activitytracker"
android:summary="%s" />
</PreferenceCategory>
<PreferenceCategory
android:key="pref_key_development"

View File

@ -129,4 +129,9 @@ public class TestDeviceSupport extends AbstractDeviceSupport {
public void onEnableHeartRateSleepSupport(boolean enable) {
}
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
}
}

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.11-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-all.zip