diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d756f9d8..4aa956f79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,24 @@ ###Changelog +####Version 0.17.5 +* Automatically start the service on boot (can be turned off) +* Pebble: PebbleKit compatibility improvements (Datalogging) +* Pebble: Display music shuffle and repeat states for some players +* Pebble 2/LE: Speed up data transfer + +####Version 0.17.4 +* Better integration with android music players +* Privacy options for calls (hide caller name/number) +* Send a notification to the connected if the Android Alarm Clock rings (com.android.deskclock) +* Fixes for cyrillic transliteration +* Pebble: Implement notification privacy modes +* Pebble: Support weather for Obisdian watchface +* Pebble: add a dev option to always and immediately ACK PebbleKit messages to the watch +* HPlus: Support alarms +* HPlus: Fix time and date sync and time format (12/24) +* HPlus: Add device specific preferences and icon +* HPlus: Support for Makibes F68 + ####Version 0.17.3 * HPlus: Improve display of new messages and phone calls * HPlus: Fix bug related to steps and heart rate diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index ca95966ba..62fedfc58 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -25,8 +25,9 @@ * Andreas Shimokawa * Carsten Pfeiffer * Daniele Gobbetti -* Julien Pivotto * João Paulo Barraca +* ivanovlev +* Julien Pivotto * Steffen Liebergeld * Lem Dulfo * Sergey Trofimov @@ -35,9 +36,11 @@ * 0nse <0nse@users.noreply.github.com> * Gergely Peidl * Christian Fischer -* Normano64 * 6arms1leg +* Normano64 +* Avamander * Ⲇⲁⲛⲓ Φi +* Yar * xzovy * xphnx * Tarik Sekmen @@ -51,6 +54,7 @@ * Kevin Richter * Kasha * Ivan +* Hasan Ammar * Gilles MOREL * Gilles Émilien MOREL * Chris Perelstein diff --git a/app/build.gradle b/app/build.gradle index a86bb12b8..efd0ed855 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -26,8 +26,8 @@ android { targetSdkVersion 23 // note: always bump BOTH versionCode and versionName! - versionName "0.17.3" - versionCode 84 + versionName "0.17.5" + versionCode 86 } buildTypes { release { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ceb2ac0bc..b4132de8f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -15,9 +15,9 @@ - + + + + + + + + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java index ea3abd190..1973e4e41 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/GBApplication.java @@ -27,7 +27,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import nodomain.freeyourgadget.gadgetbridge.database.DBConstants; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.database.DBOpenHelper; @@ -335,11 +334,7 @@ public class GBApplication extends Application { if (lockHandler != null) { lockHandler.closeDb(); } - DBHelper dbHelper = new DBHelper(context); - boolean result = true; - if (dbHelper.existsDB(DBConstants.DATABASE_NAME)) { - result = getContext().deleteDatabase(DBConstants.DATABASE_NAME); - } + boolean result = deleteOldActivityDatabase(context); result &= getContext().deleteDatabase(DATABASE_NAME); return result; } @@ -352,8 +347,8 @@ public class GBApplication extends Application { public static synchronized boolean deleteOldActivityDatabase(Context context) { DBHelper dbHelper = new DBHelper(context); boolean result = true; - if (dbHelper.existsDB(DBConstants.DATABASE_NAME)) { - result = getContext().deleteDatabase(DBConstants.DATABASE_NAME); + if (dbHelper.existsDB("ActivityDatabase")) { + result = getContext().deleteDatabase("ActivityDatabase"); } return result; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenter.java index 35f0009e7..1a2755836 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/ControlCenter.java @@ -425,8 +425,6 @@ public class ControlCenter extends GBActivity { wantedPermissions.add(Manifest.permission.READ_EXTERNAL_STORAGE); if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALENDAR) == PackageManager.PERMISSION_DENIED) wantedPermissions.add(Manifest.permission.READ_CALENDAR); - if (ContextCompat.checkSelfPermission(this, "com.fsck.k9.permission.READ_MESSAGES") == PackageManager.PERMISSION_DENIED) - wantedPermissions.add("com.fsck.k9.permission.READ_MESSAGES"); if (!wantedPermissions.isEmpty()) ActivityCompat.requestPermissions(this, wantedPermissions.toArray(new String[wantedPermissions.size()]), 0); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DbManagementActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DbManagementActivity.java index 07e0d22a5..a155c7304 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DbManagementActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DbManagementActivity.java @@ -1,11 +1,9 @@ package nodomain.freeyourgadget.gadgetbridge.activities; import android.app.AlertDialog; -import android.app.ProgressDialog; import android.content.DialogInterface; 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.view.MenuItem; @@ -18,16 +16,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; -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.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.util.FileUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; @@ -37,7 +30,6 @@ public class DbManagementActivity extends GBActivity { private Button exportDBButton; private Button importDBButton; - private Button importOldActivityDataButton; private Button deleteOldActivityDBButton; private Button deleteDBButton; private TextView dbPath; @@ -68,22 +60,7 @@ public class DbManagementActivity extends GBActivity { } }); - boolean hasOldDB = hasOldActivityDatabase(); - int oldDBVisibility = hasOldDB ? View.VISIBLE : View.GONE; - - View oldDBTitle = findViewById(R.id.mergeOldActivityDataTitle); - oldDBTitle.setVisibility(oldDBVisibility); - View oldDBText = findViewById(R.id.mergeOldActivityDataText); - oldDBText.setVisibility(oldDBVisibility); - - importOldActivityDataButton = (Button) findViewById(R.id.mergeOldActivityData); - importOldActivityDataButton.setVisibility(oldDBVisibility); - importOldActivityDataButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mergeOldActivityDbContents(); - } - }); + int oldDBVisibility = hasOldActivityDatabase() ? View.VISIBLE : View.GONE; deleteOldActivityDBButton = (Button) findViewById(R.id.deleteOldActivityDB); deleteOldActivityDBButton.setVisibility(oldDBVisibility); @@ -104,7 +81,7 @@ public class DbManagementActivity extends GBActivity { } private boolean hasOldActivityDatabase() { - return new DBHelper(this).getOldActivityDatabaseHandler() != null; + return new DBHelper(this).existsDB("ActivityDatabase"); } private String getExternalPath() { @@ -156,67 +133,6 @@ public class DbManagementActivity extends GBActivity { .show(); } - private void mergeOldActivityDbContents() { - final DBHelper helper = new DBHelper(getBaseContext()); - final ActivityDatabaseHandler oldHandler = helper.getOldActivityDatabaseHandler(); - if (oldHandler == null) { - GB.toast(this, getString(R.string.dbmanagementactivity_no_old_activitydatabase_found), Toast.LENGTH_LONG, GB.ERROR); - return; - } - selectDeviceForMergingActivityDatabaseInto(new DeviceSelectionCallback() { - @Override - public void invoke(final GBDevice device) { - if (device == null) { - GB.toast(DbManagementActivity.this, getString(R.string.dbmanagementactivity_no_connected_device), Toast.LENGTH_LONG, GB.ERROR); - return; - } - try (DBHandler targetHandler = GBApplication.acquireDB()) { - final ProgressDialog progress = ProgressDialog.show(DbManagementActivity.this, getString(R.string.dbmanagementactivity_merging_activity_data_title), getString(R.string.dbmanagementactivity_please_wait_while_merging), true, false); - new AsyncTask() { - @Override - protected Object doInBackground(Object[] params) { - helper.importOldDb(oldHandler, device, targetHandler); - if (!isFinishing() && !isDestroyed()) { - progress.dismiss(); - } - return null; - } - }.execute((Object[]) null); - } catch (Exception ex) { - GB.toast(DbManagementActivity.this, getString(R.string.dbmanagementactivity_error_importing_old_activity_data), Toast.LENGTH_LONG, GB.ERROR, ex); - } - } - }); - } - - private void selectDeviceForMergingActivityDatabaseInto(final DeviceSelectionCallback callback) { - GBDevice connectedDevice = ((GBApplication)getApplication()).getDeviceManager().getSelectedDevice(); - if (connectedDevice == null) { - callback.invoke(null); - return; - } - final List availableDevices = Collections.singletonList(connectedDevice); - GBDeviceAdapter adapter = new GBDeviceAdapter(getBaseContext(), availableDevices); - - new AlertDialog.Builder(this) - .setCancelable(true) - .setTitle(R.string.dbmanagementactivity_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(R.string.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) @@ -271,8 +187,4 @@ public class DbManagementActivity extends GBActivity { } return super.onOptionsItemSelected(item); } - - public interface DeviceSelectionCallback { - void invoke(GBDevice device); - } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java index 8016041e2..e42f1bd85 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DiscoveryActivity.java @@ -293,6 +293,7 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC DeviceType deviceType = DeviceHelper.getInstance().getSupportedType(candidate); if (deviceType.isSupported()) { candidate.setDeviceType(deviceType); + LOG.info("Recognized supported device: " + candidate); int index = deviceCandidates.indexOf(candidate); if (index >= 0) { deviceCandidates.set(index, candidate); // replace @@ -506,6 +507,7 @@ public class DiscoveryActivity extends GBActivity implements AdapterView.OnItemC stopDiscovery(); DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(deviceCandidate); + LOG.info("Using device candidate " + deviceCandidate + " with coordinator: " + coordinator.getClass()); Class pairingActivity = coordinator.getPairingActivity(); if (pairingActivity != null) { Intent intent = new Intent(this, pairingActivity); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/OnboardingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/OnboardingActivity.java deleted file mode 100644 index 043c3741c..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/OnboardingActivity.java +++ /dev/null @@ -1,78 +0,0 @@ -package nodomain.freeyourgadget.gadgetbridge.activities; - -import android.app.ProgressDialog; -import android.os.AsyncTask; -import android.os.Bundle; -import android.view.View; -import android.widget.Button; -import android.widget.TextView; -import android.widget.Toast; - -import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.R; -import nodomain.freeyourgadget.gadgetbridge.database.ActivityDatabaseHandler; -import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; -import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; -import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; -import nodomain.freeyourgadget.gadgetbridge.util.GB; - -public class OnboardingActivity extends GBActivity { - - private Button importOldActivityDataButton; - private TextView importOldActivityDataText; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_onboarding); - - Bundle extras = getIntent().getExtras(); - - GBDevice device; - if (extras != null) { - device = extras.getParcelable(GBDevice.EXTRA_DEVICE); - } else { - throw new IllegalArgumentException("Must provide a device when invoking this activity"); - } - - importOldActivityDataText = (TextView) findViewById(R.id.textview_import_old_activitydata); - importOldActivityDataText.setText(String.format(getString(R.string.import_old_db_information), device.getName())); - importOldActivityDataButton = (Button) findViewById(R.id.button_import_old_activitydata); - final GBDevice finalDevice = device; - importOldActivityDataButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - mergeOldActivityDbContents(finalDevice); - } - }); - } - - private void mergeOldActivityDbContents(final GBDevice device) { - if (device == null) { - return; - } - - 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; - } - - try (DBHandler targetHandler = GBApplication.acquireDB()) { - final ProgressDialog progress = ProgressDialog.show(OnboardingActivity.this, "Merging Activity Data", "Please wait while merging your activity data...", true, false); - new AsyncTask() { - @Override - protected Object doInBackground(Object[] params) { - helper.importOldDb(oldHandler, device, targetHandler); - progress.dismiss(); - finish(); - return null; - } - }.execute((Object[]) null); - } catch (Exception ex) { - GB.toast(OnboardingActivity.this, "Error importing old activity data into new database.", Toast.LENGTH_LONG, GB.ERROR, ex); - } - } - -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java new file mode 100644 index 000000000..572036943 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractWeekChartFragment.java @@ -0,0 +1,272 @@ +package nodomain.freeyourgadget.gadgetbridge.activities.charts; + +import android.graphics.Color; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.github.mikephil.charting.charts.BarChart; +import com.github.mikephil.charting.charts.Chart; +import com.github.mikephil.charting.charts.PieChart; +import com.github.mikephil.charting.components.LimitLine; +import com.github.mikephil.charting.components.XAxis; +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.PieData; +import com.github.mikephil.charting.data.PieDataSet; +import com.github.mikephil.charting.data.PieEntry; +import com.github.mikephil.charting.formatter.IValueFormatter; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; +import java.util.Locale; + +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; + + +public abstract class AbstractWeekChartFragment extends AbstractChartFragment { + protected static final Logger LOG = LoggerFactory.getLogger(AbstractWeekChartFragment.class); + + private Locale mLocale; + private int mTargetValue = 0; + + private PieChart mTodayPieChart; + private BarChart mWeekChart; + + @Override + 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 + DayData dayData = refreshDayPie(db, day, device); + DefaultChartsData weekBeforeData = refreshWeekBeforeData(db, mWeekChart, day, device); + + return new MyChartsData(dayData, weekBeforeData); + } + + @Override + protected void updateChartsnUIThread(ChartsData chartsData) { + MyChartsData mcd = (MyChartsData) chartsData; + +// setupLegend(mWeekChart); + mTodayPieChart.setCenterText(mcd.getDayData().centerText); + mTodayPieChart.setData(mcd.getDayData().data); + + mWeekChart.setData(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317 + mWeekChart.setData(mcd.getWeekBeforeData().getData()); + mWeekChart.getLegend().setEnabled(false); + mWeekChart.getXAxis().setValueFormatter(mcd.getWeekBeforeData().getXValueFormatter()); + } + + @Override + protected void renderCharts() { + mWeekChart.invalidate(); + mTodayPieChart.invalidate(); + } + + private DefaultChartsData refreshWeekBeforeData(DBHandler db, BarChart barChart, Calendar day, GBDevice device) { + + ActivityAnalysis analysis = new ActivityAnalysis(); + + day = (Calendar) day.clone(); // do not modify the caller's argument + day.add(Calendar.DATE, -7); + List entries = new ArrayList<>(); + ArrayList labels = new ArrayList(); + + for (int counter = 0; counter < 7; counter++) { + entries.add(new BarEntry(counter, getTotalForSamples(getSamplesOfDay(db, day, device)))); + labels.add(day.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, mLocale)); + day.add(Calendar.DATE, 1); + } + + BarDataSet set = new BarDataSet(entries, ""); + set.setColor(getMainColor()); + set.setValueFormatter(getFormatter()); + + BarData barData = new BarData(set); + barData.setValueTextColor(Color.GRAY); //prevent tearing other graph elements with the black text. Another approach would be to hide the values cmpletely with data.setDrawValues(false); + + LimitLine target = new LimitLine(mTargetValue); + barChart.getAxisLeft().removeAllLimitLines(); + barChart.getAxisLeft().addLimitLine(target); + + return new DefaultChartsData(barData, new PreformattedXIndexLabelFormatter(labels)); + } + + + private DayData refreshDayPie(DBHandler db, Calendar day, GBDevice device) { + + int totalValue = getTotalForSamples(getSamplesOfDay(db, day, device)); + + PieData data = new PieData(); + List entries = new ArrayList<>(); + List colors = new ArrayList<>(); + + entries.add(new PieEntry(totalValue, "")); //we don't want labels on the pie chart + colors.add(getMainColor()); + + if (totalValue < mTargetValue) { + entries.add(new PieEntry((mTargetValue - totalValue))); //we don't want labels on the pie chart + colors.add(Color.GRAY); + } + + PieDataSet set = new PieDataSet(entries, ""); + set.setValueFormatter(getFormatter()); + set.setColors(colors); + 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); + + return new DayData(data, formatPieValue(totalValue)); + } + + protected abstract String formatPieValue(int value); + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + mLocale = getResources().getConfiguration().locale; + + View rootView = inflater.inflate(R.layout.fragment_weeksteps_chart, container, false); + + int goal = getGoal(); + if (goal >= 0) { + mTargetValue = goal; + } + + mTodayPieChart = (PieChart) rootView.findViewById(R.id.todaystepschart); + mWeekChart = (BarChart) rootView.findViewById(R.id.weekstepschart); + + setupWeekChart(); + setupTodayPieChart(); + + // refresh immediately instead of use refreshIfVisible(), for perceived performance + refresh(); + + return rootView; + } + + private void setupTodayPieChart() { + mTodayPieChart.setBackgroundColor(BACKGROUND_COLOR); + mTodayPieChart.getDescription().setTextColor(DESCRIPTION_COLOR); + mTodayPieChart.getDescription().setText(getContext().getString(R.string.weeksteps_today_steps_description, String.valueOf(mTargetValue))); +// mTodayPieChart.setNoDataTextDescription(""); + mTodayPieChart.setNoDataText(""); + mTodayPieChart.getLegend().setEnabled(false); +// setupLegend(mTodayPieChart); + } + + private void setupWeekChart() { + mWeekChart.setBackgroundColor(BACKGROUND_COLOR); + mWeekChart.getDescription().setTextColor(DESCRIPTION_COLOR); + mWeekChart.getDescription().setText(""); + mWeekChart.setFitBars(true); + + configureBarLineChartDefaults(mWeekChart); + + XAxis x = mWeekChart.getXAxis(); + x.setDrawLabels(true); + x.setDrawGridLines(false); + x.setEnabled(true); + x.setTextColor(CHART_TEXT_COLOR); + x.setDrawLimitLinesBehindData(true); + x.setPosition(XAxis.XAxisPosition.BOTTOM); + + YAxis y = mWeekChart.getAxisLeft(); + y.setDrawGridLines(false); + y.setDrawTopYLabelEntry(false); + y.setTextColor(CHART_TEXT_COLOR); + y.setDrawZeroLine(true); + y.setSpaceBottom(0); + y.setAxisMinimum(0); + + y.setEnabled(true); + + YAxis yAxisRight = mWeekChart.getAxisRight(); + yAxisRight.setDrawGridLines(false); + yAxisRight.setEnabled(false); + yAxisRight.setDrawLabels(false); + yAxisRight.setDrawTopYLabelEntry(false); + yAxisRight.setTextColor(CHART_TEXT_COLOR); + } + + @Override + protected void setupLegend(Chart chart) { +// List legendColors = new ArrayList<>(1); +// List 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 getSamplesOfDay(DBHandler db, Calendar day, GBDevice device) { + int startTs; + int endTs; + + day = (Calendar) day.clone(); // do not modify the caller's argument + day.set(Calendar.HOUR_OF_DAY, 0); + day.set(Calendar.MINUTE, 0); + day.set(Calendar.SECOND, 0); + startTs = (int) (day.getTimeInMillis() / 1000); + + day.set(Calendar.HOUR_OF_DAY, 23); + day.set(Calendar.MINUTE, 59); + day.set(Calendar.SECOND, 59); + endTs = (int) (day.getTimeInMillis() / 1000); + + return getSamples(db, device, startTs, endTs); + } + + @Override + protected List getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { + return super.getAllSamples(db, device, tsFrom, tsTo); + } + + private static class DayData { + private final PieData data; + private final CharSequence centerText; + + DayData(PieData data, String centerText) { + this.data = data; + this.centerText = centerText; + } + } + + private static class MyChartsData extends ChartsData { + private final DefaultChartsData weekBeforeData; + private final DayData dayData; + + MyChartsData(DayData dayData, DefaultChartsData weekBeforeData) { + this.dayData = dayData; + this.weekBeforeData = weekBeforeData; + } + + DayData getDayData() { + return dayData; + } + + DefaultChartsData getWeekBeforeData() { + return weekBeforeData; + } + } + + abstract int getGoal(); + + abstract int getTotalForSamples(List activitySamples); + + abstract IValueFormatter getFormatter(); + + abstract Integer getMainColor(); +} + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityAnalysis.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityAnalysis.java index 8d0719f23..1d3d3669e 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityAnalysis.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivityAnalysis.java @@ -7,8 +7,8 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts; import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; -public class ActivityAnalysis { - public ActivityAmounts calculateActivityAmounts(List samples) { +class ActivityAnalysis { + ActivityAmounts calculateActivityAmounts(List 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); @@ -64,7 +64,7 @@ public class ActivityAnalysis { return result; } - public int calculateTotalSteps(List samples) { + int calculateTotalSteps(List samples) { int totalSteps = 0; for (ActivitySample sample : samples) { int steps = sample.getSteps(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsActivity.java index 292202cca..2b897d516 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ChartsActivity.java @@ -56,7 +56,7 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts private final String mDuration; private TextView durationLabel; - public ShowDurationDialog(String duration, Context context) { + ShowDurationDialog(String duration, Context context) { super(context); mDuration = duration; } @@ -298,7 +298,7 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts */ public class SectionsPagerAdapter extends AbstractFragmentPagerAdapter { - public SectionsPagerAdapter(FragmentManager fm) { + SectionsPagerAdapter(FragmentManager fm) { super(fm); } @@ -311,8 +311,10 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts case 1: return new SleepChartFragment(); case 2: - return new WeekStepsChartFragment(); + return new WeekSleepChartFragment(); case 3: + return new WeekStepsChartFragment(); + case 4: return new LiveActivityFragment(); } @@ -321,8 +323,8 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts @Override public int getCount() { - // Show 3 total pages. - return 4; + // Show 5 total pages. + return 5; } @Override @@ -333,8 +335,10 @@ public class ChartsActivity extends AbstractGBFragmentActivity implements Charts case 1: return getString(R.string.sleepchart_your_sleep); case 2: - return getString(R.string.weekstepschart_steps_a_week); + return getString(R.string.weeksleepchart_sleep_a_week); case 3: + return getString(R.string.weekstepschart_steps_a_week); + case 4: return getString(R.string.liveactivity_live_activity); } return super.getPageTitle(position); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekSleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekSleepChartFragment.java new file mode 100644 index 000000000..9e85d1449 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekSleepChartFragment.java @@ -0,0 +1,60 @@ +package nodomain.freeyourgadget.gadgetbridge.activities.charts; + +import com.github.mikephil.charting.data.Entry; +import com.github.mikephil.charting.formatter.IValueFormatter; +import com.github.mikephil.charting.utils.ViewPortHandler; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmount; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts; +import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; + +public class WeekSleepChartFragment extends AbstractWeekChartFragment { + @Override + public String getTitle() { + return getString(R.string.weeksleepchart_sleep_a_week); + } + + @Override + int getGoal() { + return 8 * 60; // FIXME + } + + @Override + int getTotalForSamples(List activitySamples) { + ActivityAnalysis analysis = new ActivityAnalysis(); + ActivityAmounts amounts = analysis.calculateActivityAmounts(activitySamples); + long totalSeconds = 0; + for (ActivityAmount amount : amounts.getAmounts()) { + if ((amount.getActivityKind() & ActivityKind.TYPE_SLEEP) != 0) { + totalSeconds += amount.getTotalSeconds(); + } + } + return (int) (totalSeconds / 60); + } + + @Override + protected String formatPieValue(int value) { + return DateTimeUtils.formatDurationHoursMinutes((long) value, TimeUnit.MINUTES); + } + + @Override + IValueFormatter getFormatter() { + return new IValueFormatter() { + @Override + public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) { + return formatPieValue((int) value); + } + }; + } + + @Override + Integer getMainColor() { + return akLightSleep.color; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekStepsChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekStepsChartFragment.java index 3d08ed5e6..5439f2531 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekStepsChartFragment.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/WeekStepsChartFragment.java @@ -1,267 +1,47 @@ package nodomain.freeyourgadget.gadgetbridge.activities.charts; -import android.graphics.Color; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; +import com.github.mikephil.charting.formatter.IValueFormatter; -import com.github.mikephil.charting.charts.BarChart; -import com.github.mikephil.charting.charts.Chart; -import com.github.mikephil.charting.charts.PieChart; -import com.github.mikephil.charting.components.LimitLine; -import com.github.mikephil.charting.components.XAxis; -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.PieData; -import com.github.mikephil.charting.data.PieDataSet; -import com.github.mikephil.charting.data.PieEntry; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.text.NumberFormat; -import java.util.ArrayList; -import java.util.Calendar; import java.util.List; -import java.util.Locale; import nodomain.freeyourgadget.gadgetbridge.R; -import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; - -public class WeekStepsChartFragment extends AbstractChartFragment { - protected static final Logger LOG = LoggerFactory.getLogger(WeekStepsChartFragment.class); - - private Locale mLocale; - private int mTargetSteps = 10000; - - private PieChart mTodayStepsChart; - private BarChart mWeekStepsChart; - - @Override - 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(null); // workaround for https://github.com/PhilJay/MPAndroidChart/issues/2317 - mWeekStepsChart.setData(mcd.getWeekBeforeStepsData().getData()); - mWeekStepsChart.getLegend().setEnabled(false); - mWeekStepsChart.getXAxis().setValueFormatter(mcd.getWeekBeforeStepsData().getXValueFormatter()); - } - - @Override - protected void renderCharts() { - mWeekStepsChart.invalidate(); - mTodayStepsChart.invalidate(); - } - - private DefaultChartsData refreshWeekBeforeSteps(DBHandler db, BarChart barChart, Calendar day, GBDevice device) { - - ActivityAnalysis analysis = new ActivityAnalysis(); - - day = (Calendar) day.clone(); // do not modify the caller's argument - day.add(Calendar.DATE, -7); - List entries = new ArrayList<>(); - ArrayList labels = new ArrayList(); - - for (int counter = 0; counter < 7; counter++) { - entries.add(new BarEntry(counter, analysis.calculateTotalSteps(getSamplesOfDay(db, day, device)))); - labels.add(day.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, mLocale)); - day.add(Calendar.DATE, 1); - } - - BarDataSet set = new BarDataSet(entries, ""); - set.setColor(akActivity.color); - - BarData barData = new BarData(set); - barData.setValueTextColor(Color.GRAY); //prevent tearing other graph elements with the black text. Another approach would be to hide the values cmpletely with data.setDrawValues(false); - - LimitLine target = new LimitLine(mTargetSteps); - barChart.getAxisLeft().removeAllLimitLines(); - barChart.getAxisLeft().addLimitLine(target); - - return new DefaultChartsData(barData, new PreformattedXIndexLabelFormatter(labels)); - } - - - private DaySteps refreshDaySteps(DBHandler db, Calendar day, GBDevice device) { - ActivityAnalysis analysis = new ActivityAnalysis(); - - int totalSteps = analysis.calculateTotalSteps(getSamplesOfDay(db, day, device)); - - PieData data = new PieData(); - List entries = new ArrayList<>(); - List colors = new ArrayList<>(); - - entries.add(new PieEntry(totalSteps, "")); //we don't want labels on the pie chart - colors.add(akActivity.color); - - if (totalSteps < mTargetSteps) { - entries.add(new PieEntry((mTargetSteps - totalSteps))); //we don't want labels on the pie chart - colors.add(Color.GRAY); - } - - PieDataSet set = new PieDataSet(entries, ""); - set.setColors(colors); - 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); - - return new DaySteps(data, totalSteps); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - mLocale = getResources().getConfiguration().locale; - - View rootView = inflater.inflate(R.layout.fragment_weeksteps_chart, container, false); - - GBDevice device = getChartsHost().getDevice(); - if (device != null) { - // TODO: eek, this is device specific! - mTargetSteps = MiBandCoordinator.getFitnessGoal(device.getAddress()); - } - - mTodayStepsChart = (PieChart) rootView.findViewById(R.id.todaystepschart); - mWeekStepsChart = (BarChart) rootView.findViewById(R.id.weekstepschart); - - setupWeekStepsChart(); - setupTodayStepsChart(); - - // refresh immediately instead of use refreshIfVisible(), for perceived performance - refresh(); - - return rootView; - } - +public class WeekStepsChartFragment extends AbstractWeekChartFragment { @Override public String getTitle() { return getString(R.string.weekstepschart_steps_a_week); } - private void setupTodayStepsChart() { - mTodayStepsChart.setBackgroundColor(BACKGROUND_COLOR); - mTodayStepsChart.getDescription().setTextColor(DESCRIPTION_COLOR); - mTodayStepsChart.getDescription().setText(getContext().getString(R.string.weeksteps_today_steps_description, String.valueOf(mTargetSteps))); -// mTodayStepsChart.setNoDataTextDescription(""); - mTodayStepsChart.setNoDataText(""); - mTodayStepsChart.getLegend().setEnabled(false); -// setupLegend(mTodayStepsChart); - } - - private void setupWeekStepsChart() { - mWeekStepsChart.setBackgroundColor(BACKGROUND_COLOR); - mWeekStepsChart.getDescription().setTextColor(DESCRIPTION_COLOR); - mWeekStepsChart.getDescription().setText(""); - mWeekStepsChart.setFitBars(true); - - configureBarLineChartDefaults(mWeekStepsChart); - - XAxis x = mWeekStepsChart.getXAxis(); - x.setDrawLabels(true); - x.setDrawGridLines(false); - x.setEnabled(true); - x.setTextColor(CHART_TEXT_COLOR); - x.setDrawLimitLinesBehindData(true); - x.setPosition(XAxis.XAxisPosition.BOTTOM); - - YAxis y = mWeekStepsChart.getAxisLeft(); - y.setDrawGridLines(false); - y.setDrawTopYLabelEntry(false); - y.setTextColor(CHART_TEXT_COLOR); - y.setDrawZeroLine(true); - y.setSpaceBottom(0); - y.setAxisMinimum(0); - - y.setEnabled(true); - - YAxis yAxisRight = mWeekStepsChart.getAxisRight(); - yAxisRight.setDrawGridLines(false); - yAxisRight.setEnabled(false); - yAxisRight.setDrawLabels(false); - yAxisRight.setDrawTopYLabelEntry(false); - yAxisRight.setTextColor(CHART_TEXT_COLOR); + @Override + int getGoal() { + GBDevice device = getChartsHost().getDevice(); + if (device != null) { + return MiBandCoordinator.getFitnessGoal(device.getAddress()); + } + return -1; } @Override - protected void setupLegend(Chart chart) { -// List legendColors = new ArrayList<>(1); -// List 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 getSamplesOfDay(DBHandler db, Calendar day, GBDevice device) { - int startTs; - int endTs; - - day = (Calendar) day.clone(); // do not modify the caller's argument - day.set(Calendar.HOUR_OF_DAY, 0); - day.set(Calendar.MINUTE, 0); - day.set(Calendar.SECOND, 0); - startTs = (int) (day.getTimeInMillis() / 1000); - - day.set(Calendar.HOUR_OF_DAY, 23); - day.set(Calendar.MINUTE, 59); - day.set(Calendar.SECOND, 59); - endTs = (int) (day.getTimeInMillis() / 1000); - - return getSamples(db, device, startTs, endTs); + int getTotalForSamples(List activitySamples) { + ActivityAnalysis analysis = new ActivityAnalysis(); + return analysis.calculateTotalSteps(activitySamples); } @Override - protected List getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) { - return super.getAllSamples(db, device, tsFrom, tsTo); + protected String formatPieValue(int value) { + return String.valueOf(value); } - private static class DaySteps { - private final PieData data; - private final int totalSteps; - - public DaySteps(PieData data, int totalSteps) { - this.data = data; - this.totalSteps = totalSteps; - } + @Override + IValueFormatter getFormatter() { + return null; } - 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; - } + @Override + Integer getMainColor() { + return akActivity.color; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapter.java index 3e3a25a2b..18fabf095 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/GBDeviceAdapter.java @@ -134,6 +134,14 @@ public class GBDeviceAdapter extends ArrayAdapter { deviceImageView.setImageResource(R.drawable.ic_device_lovetoy_disabled); } break; + case HPLUS: + case MAKIBESF68: + if( device.isConnected()) { + deviceImageView.setImageResource(R.drawable.ic_device_hplus); + } else { + deviceImageView.setImageResource(R.drawable.ic_device_hplus_disabled); + } + break; default: if (device.isConnected()) { deviceImageView.setImageResource(R.drawable.ic_launcher); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/contentprovider/PebbleContentProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/contentprovider/PebbleContentProvider.java index e16c1bdc6..384e28744 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/contentprovider/PebbleContentProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/contentprovider/PebbleContentProvider.java @@ -58,17 +58,17 @@ public class PebbleContentProvider extends ContentProvider { if (uri.equals(CONTENT_URI)) { MatrixCursor mc = new MatrixCursor(columnNames); int connected = 0; - int appMessage = 0; + int pebbleKit = 0; Prefs prefs = GBApplication.getPrefs(); if (prefs.getBoolean("pebble_enable_pebblekit", false)) { - appMessage = 1; + pebbleKit = 1; } String fwString = "unknown"; if (mGBDevice != null && mGBDevice.getType() == DeviceType.PEBBLE && mGBDevice.isInitialized()) { connected = 1; fwString = mGBDevice.getFirmwareVersion(); } - mc.addRow(new Object[]{connected, appMessage, 0, 3, 8, 2, fwString}); + mc.addRow(new Object[]{connected, pebbleKit, pebbleKit, 3, 8, 2, fwString}); return mc; } else { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java deleted file mode 100644 index 221cf410a..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java +++ /dev/null @@ -1,105 +0,0 @@ -package nodomain.freeyourgadget.gadgetbridge.database; - -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.widget.Toast; - -import java.io.File; - -import nodomain.freeyourgadget.gadgetbridge.database.schema.ActivityDBCreationScript; -import nodomain.freeyourgadget.gadgetbridge.database.schema.SchemaMigration; -import nodomain.freeyourgadget.gadgetbridge.entities.DaoMaster; -import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; -import nodomain.freeyourgadget.gadgetbridge.util.GB; - -import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.DATABASE_NAME; -import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TIMESTAMP; -import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_GBACTIVITYSAMPLES; - -/** - * @deprecated can be removed entirely, only used for backwards compatibility - */ -public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandler { - - private static final int DATABASE_VERSION = 7; - private static final String UPDATER_CLASS_NAME_PREFIX = "ActivityDBUpdate_"; - private final Context context; - - public ActivityDatabaseHandler(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - this.context = context; - } - - @Override - public void onCreate(SQLiteDatabase db) { - try { - ActivityDBCreationScript script = new ActivityDBCreationScript(); - script.createSchema(db); - } catch (RuntimeException ex) { - GB.toast("Error creating database.", Toast.LENGTH_SHORT, GB.ERROR, ex); - } - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - new SchemaMigration(UPDATER_CLASS_NAME_PREFIX).onUpgrade(db, oldVersion, newVersion); - } - - @Override - public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { - new SchemaMigration(UPDATER_CLASS_NAME_PREFIX).onDowngrade(db, oldVersion, newVersion); - } - - @Override - public SQLiteDatabase getDatabase() { - return super.getWritableDatabase(); - } - - @Override - public void closeDb() { - } - - @Override - public void openDb() { - } - - @Override - public SQLiteOpenHelper getHelper() { - return this; - } - - public Context getContext() { - return context; - } - - public boolean hasContent() { - File dbFile = getContext().getDatabasePath(getDatabaseName()); - if (dbFile == null || !dbFile.exists()) { - return false; - } - - 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(); - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBConstants.java deleted file mode 100644 index 671b5af38..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBConstants.java +++ /dev/null @@ -1,18 +0,0 @@ -package nodomain.freeyourgadget.gadgetbridge.database; - -/** - * TODO: Legacy, can be removed once migration support for old ActivityDatabase is removed - * @deprecated only for backwards compatibility - */ -public class DBConstants { - public static final String DATABASE_NAME = "ActivityDatabase"; - - public static final String TABLE_GBACTIVITYSAMPLES = "GBActivitySamples"; - - public static final String KEY_TIMESTAMP = "timestamp"; - public static final String KEY_PROVIDER = "provider"; - public static final String KEY_INTENSITY = "intensity"; - public static final String KEY_STEPS = "steps"; - public static final String KEY_CUSTOM_SHORT = "customShort"; - public static final String KEY_TYPE = "type"; -} 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 35f0efbf7..c6d894f9a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHelper.java @@ -6,7 +6,6 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.widget.Toast; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,7 +13,6 @@ import org.slf4j.LoggerFactory; 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; @@ -26,11 +24,7 @@ import de.greenrobot.dao.query.Query; import de.greenrobot.dao.query.QueryBuilder; import de.greenrobot.dao.query.WhereCondition; import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.devices.AbstractSampleProvider; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; -import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleHealthSampleProvider; -import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleMisfitSampleProvider; -import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample; import nodomain.freeyourgadget.gadgetbridge.entities.ActivityDescription; import nodomain.freeyourgadget.gadgetbridge.entities.ActivityDescriptionDao; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; @@ -38,29 +32,18 @@ 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.PebbleHealthActivityOverlay; -import nodomain.freeyourgadget.gadgetbridge.entities.PebbleHealthActivityOverlayDao; import nodomain.freeyourgadget.gadgetbridge.entities.Tag; import nodomain.freeyourgadget.gadgetbridge.entities.TagDao; 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.ActivityKind; -import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser; 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 nodomain.freeyourgadget.gadgetbridge.util.GB; -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 @@ -547,149 +530,6 @@ public class DBHelper { return tag; } - /** - * 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.getPebbleHealthActivitySampleDao().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()) { - if (coordinator.supports(targetDevice)) { - AbstractSampleProvider sampleProvider = (AbstractSampleProvider) coordinator.getSampleProvider(targetDevice, session); - importActivitySamples(oldDB, targetDevice, session, sampleProvider, user); - break; - } - } - } - } - - private void importActivitySamples(SQLiteDatabase fromDb, GBDevice targetDevice, DaoSession targetSession, AbstractSampleProvider sampleProvider, User user) { - if (sampleProvider instanceof PebbleMisfitSampleProvider) { - GB.toast(context, "Migration of old Misfit data is not supported!", Toast.LENGTH_LONG, GB.WARN); - return; - } - - String order = "timestamp"; - final String where = "provider=" + sampleProvider.getID(); - - boolean convertActivityTypeToRange = false; - int currentTypeRun, previousTypeRun, currentTimeStamp, currentTypeStartTimeStamp, currentTypeEndTimeStamp; - List overlayList = new ArrayList<>(); - - final int BATCH_SIZE = 100000; // 100.000 samples = rougly 20 MB per batch - List newSamples; - if (sampleProvider instanceof PebbleHealthSampleProvider) { - convertActivityTypeToRange = true; - previousTypeRun = ActivitySample.NOT_MEASURED; - currentTypeStartTimeStamp = -1; - currentTypeEndTimeStamp = -1; - - } else { - previousTypeRun = currentTypeStartTimeStamp = currentTypeEndTimeStamp = 0; - } - 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(); - newSamples = new ArrayList<>(Math.min(BATCH_SIZE, cursor.getCount())); - while (cursor.moveToNext()) { - T newSample = sampleProvider.createActivitySample(); - newSample.setProvider(sampleProvider); - newSample.setUserId(userId); - newSample.setDeviceId(deviceId); - currentTimeStamp = cursor.getInt(colTimeStamp); - newSample.setTimestamp(currentTimeStamp); - newSample.setRawIntensity(getNullableInt(cursor, colIntensity, ActivitySample.NOT_MEASURED)); - currentTypeRun = getNullableInt(cursor, colType, ActivitySample.NOT_MEASURED); - newSample.setRawKind(currentTypeRun); - if (convertActivityTypeToRange) { - //at the beginning there is no start timestamp - if (currentTypeStartTimeStamp == -1) { - currentTypeStartTimeStamp = currentTypeEndTimeStamp = currentTimeStamp; - previousTypeRun = currentTypeRun; - } - - if (currentTypeRun != previousTypeRun) { - //we used not to store the last sample, now we do the opposite and we need to round up - currentTypeEndTimeStamp = currentTimeStamp; - //if the Type has changed, the run has ended. Only store light and deep sleep data - if (previousTypeRun == 4) { - overlayList.add(new PebbleHealthActivityOverlay(currentTypeStartTimeStamp, currentTypeEndTimeStamp, sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP), deviceId, userId, null)); - } else if (previousTypeRun == 5) { - overlayList.add(new PebbleHealthActivityOverlay(currentTypeStartTimeStamp, currentTypeEndTimeStamp, sampleProvider.toRawActivityKind(ActivityKind.TYPE_DEEP_SLEEP), deviceId, userId, null)); - } - currentTypeStartTimeStamp = currentTimeStamp; - previousTypeRun = currentTypeRun; - } else { - //just expand the run - currentTypeEndTimeStamp = currentTimeStamp; - } - - } - newSample.setSteps(getNullableInt(cursor, colSteps, ActivitySample.NOT_MEASURED)); - if (colCustomShort > -1) { - newSample.setHeartRate(getNullableInt(cursor, colCustomShort, ActivitySample.NOT_MEASURED)); - } else { - newSample.setHeartRate(ActivitySample.NOT_MEASURED); - } - newSamples.add(newSample); - - if ((newSamples.size() % BATCH_SIZE) == 0) { - sampleProvider.getSampleDao().insertOrReplaceInTx(newSamples, true); - targetSession.clear(); - newSamples.clear(); - } - } - // and insert the remaining samples - if (!newSamples.isEmpty()) { - sampleProvider.getSampleDao().insertOrReplaceInTx(newSamples, true); - } - // store the overlay records - if (!overlayList.isEmpty()) { - PebbleHealthActivityOverlayDao overlayDao = targetSession.getPebbleHealthActivityOverlayDao(); - overlayDao.insertOrReplaceInTx(overlayList); - } - } - } - - private int getNullableInt(Cursor cursor, int columnIndex, int defaultValue) { - if (cursor.isNull(columnIndex)) { - return defaultValue; - } - return cursor.getInt(columnIndex); - } - public static void clearSession() { try (DBHandler dbHandler = GBApplication.acquireDB()) { DaoSession session = dbHandler.getDaoSession(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBCreationScript.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBCreationScript.java deleted file mode 100644 index 20c4ac5b7..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBCreationScript.java +++ /dev/null @@ -1,27 +0,0 @@ -package nodomain.freeyourgadget.gadgetbridge.database.schema; - -import android.database.sqlite.SQLiteDatabase; - -import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; - -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_PROVIDER; -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 ActivityDBCreationScript { - public void createSchema(SQLiteDatabase db) { - String CREATE_GBACTIVITYSAMPLES_TABLE = "CREATE TABLE " + TABLE_GBACTIVITYSAMPLES + " (" - + KEY_TIMESTAMP + " INT," - + KEY_PROVIDER + " TINYINT," - + KEY_INTENSITY + " SMALLINT," - + KEY_STEPS + " TINYINT," - + KEY_TYPE + " TINYINT," - + KEY_CUSTOM_SHORT + " INT," - + " PRIMARY KEY (" + KEY_TIMESTAMP + "," + KEY_PROVIDER + ") ON CONFLICT REPLACE)" + DBHelper.getWithoutRowId(); - db.execSQL(CREATE_GBACTIVITYSAMPLES_TABLE); - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBUpdate_4.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBUpdate_4.java deleted file mode 100644 index c27b3dc7c..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBUpdate_4.java +++ /dev/null @@ -1,31 +0,0 @@ -package nodomain.freeyourgadget.gadgetbridge.database.schema; - -import android.database.sqlite.SQLiteDatabase; - -import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; -import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript; - -import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_GBACTIVITYSAMPLES; - -/** - * Upgrade and downgrade with DB versions <= 5 is not supported. - * Just recreates the default schema. Those GB versions may or may not - * work with that, but this code will probably not create a DB for them - * anyway. - */ -public class ActivityDBUpdate_4 extends ActivityDBCreationScript implements DBUpdateScript { - @Override - public void upgradeSchema(SQLiteDatabase db) { - recreateSchema(db); - } - - @Override - public void downgradeSchema(SQLiteDatabase db) { - recreateSchema(db); - } - - private void recreateSchema(SQLiteDatabase db) { - DBHelper.dropTable(TABLE_GBACTIVITYSAMPLES, db); - createSchema(db); - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBUpdate_6.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBUpdate_6.java deleted file mode 100644 index b6d157c9c..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBUpdate_6.java +++ /dev/null @@ -1,27 +0,0 @@ -package nodomain.freeyourgadget.gadgetbridge.database.schema; - -import android.database.sqlite.SQLiteDatabase; - -import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; -import nodomain.freeyourgadget.gadgetbridge.database.DBUpdateScript; - -import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_CUSTOM_SHORT; -import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_GBACTIVITYSAMPLES; - -/** - * Adds a column "customShort" to the table "GBActivitySamples" - */ -public class ActivityDBUpdate_6 implements DBUpdateScript { - @Override - public void upgradeSchema(SQLiteDatabase db) { - if (!DBHelper.existsColumn(TABLE_GBACTIVITYSAMPLES, KEY_CUSTOM_SHORT, db)) { - String ADD_COLUMN_CUSTOM_SHORT = "ALTER TABLE " + TABLE_GBACTIVITYSAMPLES + " ADD COLUMN " - + KEY_CUSTOM_SHORT + " INT;"; - db.execSQL(ADD_COLUMN_CUSTOM_SHORT); - } - } - - @Override - public void downgradeSchema(SQLiteDatabase db) { - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBUpdate_7.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBUpdate_7.java deleted file mode 100644 index c7077451f..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/schema/ActivityDBUpdate_7.java +++ /dev/null @@ -1,8 +0,0 @@ -package nodomain.freeyourgadget.gadgetbridge.database.schema; - -/** - * Bugfix for users who installed 0.8.1 cleanly, i.e. without any previous - * database. Perform Update script 6 again. - */ -public class ActivityDBUpdate_7 extends ActivityDBUpdate_6 { -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/pebble/GBDeviceEventDataLogging.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/pebble/GBDeviceEventDataLogging.java new file mode 100644 index 000000000..1dd4f5431 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/pebble/GBDeviceEventDataLogging.java @@ -0,0 +1,17 @@ +package nodomain.freeyourgadget.gadgetbridge.deviceevents.pebble; + +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; + +public class GBDeviceEventDataLogging extends GBDeviceEvent { + public static final int COMMAND_RECEIVE_DATA = 1; + public static final int COMMAND_FINISH_SESSION = 2; + + public int command; + public UUID appUUID; + public long timestamp; + public long tag; + public byte pebbleDataType; + public Object[] data; +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusConstants.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusConstants.java index 73515ee7f..9f06b88c2 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusConstants.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusConstants.java @@ -14,12 +14,14 @@ public final class HPlusConstants { public static final UUID UUID_CHARACTERISTIC_MEASURE = UUID.fromString("14702853-620a-3973-7c78-9cfff0876abd"); public static final UUID UUID_SERVICE_HP = UUID.fromString("14701820-620a-3973-7c78-9cfff0876abd"); + public static final byte ARG_WRIST_LEFT = 0; //Guess... + public static final byte ARG_WRIST_RIGHT = 1; //Guess... public static final byte ARG_LANGUAGE_CN = 1; public static final byte ARG_LANGUAGE_EN = 2; - public static final byte ARG_TIMEMODE_24H = 0; - public static final byte ARG_TIMEMODE_12H = 1; + public static final byte ARG_TIMEMODE_24H = 1; + public static final byte ARG_TIMEMODE_12H = 0; public static final byte ARG_UNIT_METRIC = 0; public static final byte ARG_UNIT_IMPERIAL = 1; @@ -36,9 +38,12 @@ public final class HPlusConstants { public static final byte INCOMING_CALL_STATE_DISABLED_THRESHOLD = 0x7B; public static final byte INCOMING_CALL_STATE_ENABLED = (byte) 0xAA; + public static final byte ARG_ALARM_DISABLE = (byte) -1; + public static final byte[] CMD_SET_PREF_START = new byte[]{0x4f, 0x5a}; public static final byte[] CMD_SET_PREF_START1 = new byte[]{0x4d}; - public static final byte CMD_SET_ALARM = 0x4c; + //public static final byte CMD_SET_ALARM = 0x4c; Unknown + public static final byte CMD_SET_ALARM = 0x0c; public static final byte CMD_SET_LANGUAGE = 0x22; public static final byte CMD_SET_TIMEMODE = 0x47; public static final byte CMD_SET_UNITS = 0x48; @@ -103,15 +108,11 @@ public final class HPlusConstants { public static final String PREF_HPLUS_SCREENTIME = "hplus_screentime"; public static final String PREF_HPLUS_ALLDAYHR = "hplus_alldayhr"; - public static final String PREF_HPLUS_HR = "hplus_hr_enable"; public static final String PREF_HPLUS_UNIT = "hplus_unit"; - public static final String PREF_HPLUS_TIMEMODE = "hplus_timemode"; + public static final String PREF_HPLUS_TIMEFORMAT = "hplus_timeformat"; public static final String PREF_HPLUS_WRIST = "hplus_wrist"; - public static final String PREF_HPLUS_SWALERT = "hplus_sw_alert"; - public static final String PREF_HPLUS_ALERT_TIME = "hplus_alert_time"; public static final String PREF_HPLUS_SIT_START_TIME = "hplus_sit_start_time"; public static final String PREF_HPLUS_SIT_END_TIME = "hplus_sit_end_time"; - public static final String PREF_HPLUS_LANGUAGE = "hplus_language"; public static final Map transliterateMap = new HashMap(){ { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusCoordinator.java index 8224a1077..a86561455 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusCoordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/HPlusCoordinator.java @@ -36,6 +36,9 @@ import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.Collections; +import java.util.Locale; + +import static nodomain.freeyourgadget.gadgetbridge.GBApplication.getContext; public class HPlusCoordinator extends AbstractDeviceCoordinator { protected static final Logger LOG = LoggerFactory.getLogger(HPlusCoordinator.class); @@ -143,23 +146,42 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator { qb.where(HPlusHealthActivitySampleDao.Properties.DeviceId.eq(deviceId)).buildDelete().executeDeleteWithoutDetachingEntities(); } - public static int getFitnessGoal(String address) throws IllegalArgumentException { - ActivityUser activityUser = new ActivityUser(); - - return activityUser.getStepsGoal(); - } - public static byte getLanguage(String address) { - return (byte) prefs.getInt(HPlusConstants.PREF_HPLUS_LANGUAGE + "_" + address, HPlusConstants.ARG_LANGUAGE_EN); + String language = prefs.getString("language", "default"); + Locale locale; + if (language.equals("default")) { + locale = Locale.getDefault(); + } else { + locale = new Locale(language); + } + + if (locale.getLanguage().equals(new Locale("cn").getLanguage())){ + return HPlusConstants.ARG_LANGUAGE_CN; + }else{ + return HPlusConstants.ARG_LANGUAGE_EN; + } } public static byte getTimeMode(String address) { - return (byte) prefs.getInt(HPlusConstants.PREF_HPLUS_TIMEMODE + "_" + address, 0); + String tmode = prefs.getString(HPlusConstants.PREF_HPLUS_TIMEFORMAT, getContext().getString(R.string.p_timeformat_24h)); + + if(tmode.equals(getContext().getString(R.string.p_timeformat_24h))) { + return HPlusConstants.ARG_TIMEMODE_24H; + }else{ + return HPlusConstants.ARG_TIMEMODE_12H; + } + } public static byte getUnit(String address) { - return (byte) prefs.getInt(HPlusConstants.PREF_HPLUS_UNIT + "_" + address, 0); + String units = prefs.getString(HPlusConstants.PREF_HPLUS_UNIT, getContext().getString(R.string.p_unit_metric)); + + if(units.equals(getContext().getString(R.string.p_unit_metric))){ + return HPlusConstants.ARG_UNIT_METRIC; + }else{ + return HPlusConstants.ARG_UNIT_IMPERIAL; + } } public static byte getUserWeight(String address) { @@ -196,15 +218,17 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator { } public static byte getScreenTime(String address) { - return (byte) (prefs.getInt(HPlusConstants.PREF_HPLUS_SCREENTIME + "_" + address, 5) & 0xFF); + return (byte) (prefs.getInt(HPlusConstants.PREF_HPLUS_SCREENTIME, 5) & 0xFF); } public static byte getAllDayHR(String address) { - return (byte) (prefs.getInt(HPlusConstants.PREF_HPLUS_ALLDAYHR + "_" + address, HPlusConstants.ARG_HEARTRATE_ALLDAY_ON) & 0xFF); - } + Boolean value = (prefs.getBoolean(HPlusConstants.PREF_HPLUS_ALLDAYHR, true)); - public static byte getHRState(String address) { - return (byte) (prefs.getInt(HPlusConstants.PREF_HPLUS_HR + "_" + address, HPlusConstants.ARG_HEARTRATE_MEASURE_ON) & 0xFF); + if(value){ + return HPlusConstants.ARG_HEARTRATE_ALLDAY_ON; + }else{ + return HPlusConstants.ARG_HEARTRATE_ALLDAY_OFF; + } } public static byte getSocial(String address) { @@ -214,23 +238,21 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator { } public static byte getUserWrist(String address) { - return (byte) (prefs.getInt(HPlusConstants.PREF_HPLUS_WRIST + "_" + address, 10) & 0xFF); - } + String value = prefs.getString(HPlusConstants.PREF_HPLUS_WRIST, getContext().getString(R.string.left)); - public static boolean getSWAlertTime(String address) { - return prefs.getBoolean(HPlusConstants.PREF_HPLUS_SWALERT + "_" + address, false); - } - - public static int getAlertTime(String address) { - return prefs.getInt(HPlusConstants.PREF_HPLUS_ALERT_TIME + "_" + address, 0); + if(value.equals(getContext().getString(R.string.left))){ + return HPlusConstants.ARG_WRIST_LEFT; + }else{ + return HPlusConstants.ARG_WRIST_RIGHT; + } } public static int getSITStartTime(String address) { - return prefs.getInt(HPlusConstants.PREF_HPLUS_SIT_START_TIME + "_" + address, 0); + return prefs.getInt(HPlusConstants.PREF_HPLUS_SIT_START_TIME, 0); } public static int getSITEndTime(String address) { - return prefs.getInt(HPlusConstants.PREF_HPLUS_SIT_END_TIME + "_" + address, 0); + return prefs.getInt(HPlusConstants.PREF_HPLUS_SIT_END_TIME, 0); } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/MakibesF68Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/MakibesF68Coordinator.java index 1b1abe568..63cd71915 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/MakibesF68Coordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/hplus/MakibesF68Coordinator.java @@ -30,4 +30,10 @@ public class MakibesF68Coordinator extends HPlusCoordinator { public DeviceType getDeviceType() { return DeviceType.MAKIBESF68; } + + @Override + public String getManufacturer() { + return "Makibes"; + } + } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Coordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Coordinator.java index 3ba9e543f..7fd8ad990 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Coordinator.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Coordinator.java @@ -54,12 +54,12 @@ public class MiBand2Coordinator extends MiBandCoordinator { // and a heuristic for now try { BluetoothDevice device = candidate.getDevice(); - if (isHealthWearable(device)) { +// if (isHealthWearable(device)) { String name = device.getName(); if (name != null && name.equalsIgnoreCase(MiBandConst.MI_BAND2_NAME)) { return DeviceType.MIBAND2; } - } +// } } catch (Exception ex) { LOG.error("unable to check device support", ex); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Service.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Service.java index 0b8f5623f..fb3e9a107 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Service.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBand2Service.java @@ -31,7 +31,7 @@ public class MiBand2Service { public static final UUID UUID_UNKNOWN_CHARACTERISTIC8 = UUID.fromString("00000008-0000-3512-2118-0009af100700"); // service uuid fee1 public static final UUID UUID_CHARACTERISTIC_AUTH = UUID.fromString("00000009-0000-3512-2118-0009af100700"); - public static final UUID UUID_UNKNOWN_CHARACTERISTIC10 = UUID.fromString("00000010-0000-3512-2118-0009af100700"); + public static final UUID UUID_CHARACTERISTIC_10_BUTTON = UUID.fromString("00000010-0000-3512-2118-0009af100700"); public static final int ALERT_LEVEL_NONE = 0; public static final int ALERT_LEVEL_MESSAGE = 1; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java index 98877a833..b8e6f456d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java @@ -23,6 +23,7 @@ public final class MiBandConst { public static final String ORIGIN_INCOMING_CALL = "incoming_call"; + public static final String ORIGIN_ALARM_CLOCK = "alarm_clock"; public static final String MI_GENERAL_NAME_PREFIX = "MI"; public static final String MI_BAND2_NAME = "MI Band 2"; public static final String MI_1 = "1"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java index 9f825a7b2..1aae81608 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPairingActivity.java @@ -33,7 +33,7 @@ public class MiBandPairingActivity extends GBActivity { private static final Logger LOG = LoggerFactory.getLogger(MiBandPairingActivity.class); private static final int REQ_CODE_USER_SETTINGS = 52; - private static final String STATE_MIBAND_ADDRESS = "mibandMacAddress"; + private static final String STATE_DEVICE_CANDIDATE = "stateDeviceCandidate"; private static final long DELAY_AFTER_BONDING = 1000; // 1s private TextView message; private boolean isPairing; @@ -103,7 +103,7 @@ public class MiBandPairingActivity extends GBActivity { Intent intent = getIntent(); deviceCandidate = intent.getParcelableExtra(DeviceCoordinator.EXTRA_DEVICE_CANDIDATE); if (deviceCandidate == null && savedInstanceState != null) { - deviceCandidate = savedInstanceState.getParcelable(STATE_MIBAND_ADDRESS); + deviceCandidate = savedInstanceState.getParcelable(STATE_DEVICE_CANDIDATE); } if (deviceCandidate == null) { Toast.makeText(this, getString(R.string.message_cannot_pair_no_mac), Toast.LENGTH_SHORT).show(); @@ -125,13 +125,13 @@ public class MiBandPairingActivity extends GBActivity { @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - outState.putParcelable(STATE_MIBAND_ADDRESS, deviceCandidate); + outState.putParcelable(STATE_DEVICE_CANDIDATE, deviceCandidate); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); - deviceCandidate = savedInstanceState.getParcelable(STATE_MIBAND_ADDRESS); + deviceCandidate = savedInstanceState.getParcelable(STATE_DEVICE_CANDIDATE); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java index caeb4e2a6..129f4afe4 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java @@ -17,6 +17,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.util.GB; +import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_ALARM_CLOCK; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.ORIGIN_INCOMING_CALL; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_ACTIVATE_DISPLAY_ON_LIFT; import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MI2_DATEFORMAT; @@ -146,6 +147,7 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity { prefKeys.add(PREF_MIBAND_FITNESS_GOAL); prefKeys.add(PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR); prefKeys.add(PREF_MIBAND_DEVICE_TIME_OFFSET_HOURS); + prefKeys.add(getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_ALARM_CLOCK)); prefKeys.add(getNotificationPrefKey(VIBRATION_COUNT, ORIGIN_INCOMING_CALL)); for (NotificationType type : NotificationType.values()) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java index a045079ed..6fbcd4803 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PBWReader.java @@ -204,6 +204,9 @@ public class PBWReader { } app = new GBDeviceApp(appUUID, appName, appCreator, appVersion, appType); } + else if (!isFirmware) { + isValid = false; + } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleHealthSampleProvider.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleHealthSampleProvider.java index efff6a267..93a6835f1 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleHealthSampleProvider.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/pebble/PebbleHealthSampleProvider.java @@ -45,8 +45,8 @@ public class PebbleHealthSampleProvider extends AbstractSampleProvider qb = getSession().getPebbleHealthActivityOverlayDao().queryBuilder(); // I assume it returns the records by id ascending ... (last overlay is dominant) - qb.where(PebbleHealthActivityOverlayDao.Properties.DeviceId.eq(dbDevice.getId()), PebbleHealthActivityOverlayDao.Properties.TimestampFrom.ge(timestamp_from)) - .where(PebbleHealthActivityOverlayDao.Properties.TimestampTo.le(timestamp_to)); + qb.where(PebbleHealthActivityOverlayDao.Properties.DeviceId.eq(dbDevice.getId()), PebbleHealthActivityOverlayDao.Properties.TimestampTo.ge(timestamp_from)) + .where(PebbleHealthActivityOverlayDao.Properties.TimestampFrom.le(timestamp_to)); List overlayRecords = qb.build().list(); for (PebbleHealthActivityOverlay overlay : overlayRecords) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmClockReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmClockReceiver.java new file mode 100644 index 000000000..aa102be83 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AlarmClockReceiver.java @@ -0,0 +1,69 @@ +package nodomain.freeyourgadget.gadgetbridge.externalevents; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; + +public class AlarmClockReceiver extends BroadcastReceiver { + /** + * AlarmActivity and AlarmService (when unbound) listen for this broadcast intent + * so that other applications can snooze the alarm (after ALARM_ALERT_ACTION and before + * ALARM_DONE_ACTION). + */ + public static final String ALARM_SNOOZE_ACTION = "com.android.deskclock.ALARM_SNOOZE"; + + /** + * AlarmActivity and AlarmService listen for this broadcast intent so that other + * applications can dismiss the alarm (after ALARM_ALERT_ACTION and before ALARM_DONE_ACTION). + */ + public static final String ALARM_DISMISS_ACTION = "com.android.deskclock.ALARM_DISMISS"; + + /** A public action sent by AlarmService when the alarm has started. */ + public static final String ALARM_ALERT_ACTION = "com.android.deskclock.ALARM_ALERT"; + + /** A public action sent by AlarmService when the alarm has stopped for any reason. */ + public static final String ALARM_DONE_ACTION = "com.android.deskclock.ALARM_DONE"; + private int lastId; + + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (ALARM_ALERT_ACTION.equals(action)) { + sendAlarm(true); + } else if (ALARM_DONE_ACTION.equals(action)) { + sendAlarm(false); + } + } + + + + private synchronized void sendAlarm(boolean on) { + dismissLastAlarm(); + if (on) { + lastId = generateId(); + NotificationSpec spec = new NotificationSpec(); + spec.type = NotificationType.GENERIC_ALARM_CLOCK; + spec.id = lastId; + spec.sourceName = "ALARMCLOCKRECEIVER"; + // can we get the alarm title somehow? + GBApplication.deviceService().onNotification(spec); + } + } + + private void dismissLastAlarm() { + if (lastId != 0) { + GBApplication.deviceService().onDeleteNotification(lastId); + lastId = 0; + } + } + + private int generateId() { + // lacks negative values, but should be sufficient + return (int) (Math.random() * Integer.MAX_VALUE); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AutoStartReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AutoStartReceiver.java new file mode 100644 index 000000000..c6c194d07 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/AutoStartReceiver.java @@ -0,0 +1,20 @@ +package nodomain.freeyourgadget.gadgetbridge.externalevents; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; + +public class AutoStartReceiver extends BroadcastReceiver { + private static final String TAG = AutoStartReceiver.class.getName(); + + @Override + public void onReceive(Context context, Intent intent) { + if (GBApplication.getGBPrefs().getAutoStart()) { + Log.i(TAG, "Boot completed, starting Gadgetbridge"); + GBApplication.deviceService().start(); + } + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothStateChangeReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothStateChangeReceiver.java index 0e639733f..4d5018cb7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothStateChangeReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/BluetoothStateChangeReceiver.java @@ -6,11 +6,16 @@ import android.content.Context; import android.content.Intent; import android.support.v4.content.LocalBroadcastManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class BluetoothStateChangeReceiver extends BroadcastReceiver { + private static final Logger LOG = LoggerFactory.getLogger(BluetoothStateChangeReceiver.class); + @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); @@ -26,6 +31,7 @@ public class BluetoothStateChangeReceiver extends BroadcastReceiver { return; } + LOG.info("Bluetooth turned on => connecting..."); GBApplication.deviceService().connect(); } else if (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1) == BluetoothAdapter.STATE_OFF) { GBApplication.quit(); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/K9Receiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/K9Receiver.java deleted file mode 100644 index c9431b43b..000000000 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/K9Receiver.java +++ /dev/null @@ -1,87 +0,0 @@ -package nodomain.freeyourgadget.gadgetbridge.externalevents; - -import android.app.NotificationManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.PowerManager; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import nodomain.freeyourgadget.gadgetbridge.GBApplication; -import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; -import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; -import nodomain.freeyourgadget.gadgetbridge.util.Prefs; - -public class K9Receiver extends BroadcastReceiver { - - private static final Logger LOG = LoggerFactory.getLogger(K9Receiver.class); - private final Uri k9Uri = Uri.parse("content://com.fsck.k9.messageprovider/inbox_messages"); - - @Override - public void onReceive(Context context, Intent intent) { - - Prefs prefs = GBApplication.getPrefs(); - if ("never".equals(prefs.getString("notification_mode_k9mail", "when_screen_off"))) { - return; - } - if ("when_screen_off".equals(prefs.getString("notification_mode_k9mail", "when_screen_off"))) { - PowerManager powermanager = (PowerManager) context.getSystemService(Context.POWER_SERVICE); - if (powermanager.isScreenOn()) { - return; - } - } - switch (GBApplication.getGrantedInterruptionFilter()) { - case NotificationManager.INTERRUPTION_FILTER_ALL: - break; - case NotificationManager.INTERRUPTION_FILTER_ALARMS: - case NotificationManager.INTERRUPTION_FILTER_NONE: - case NotificationManager.INTERRUPTION_FILTER_PRIORITY: - return; - } - - String uriWanted = intent.getData().toString(); - - String[] messagesProjection = { - "senderAddress", - "subject", - "preview", - "uri" - }; - - NotificationSpec notificationSpec = new NotificationSpec(); - notificationSpec.id = -1; - notificationSpec.type = NotificationType.GENERIC_EMAIL; - - /* - * there seems to be no way to specify the uri in the where clause. - * If we do so, we just get the newest message, not the one requested. - * So, we will just search our message and match the uri manually. - * It should be the first one returned by the query in most cases, - */ - - try (Cursor c = context.getContentResolver().query(k9Uri, messagesProjection, null, null, null)) { - if (c != null) { - while (c.moveToNext()) { - String uri = c.getString(c.getColumnIndex("uri")); - if (uri.equals(uriWanted)) { - notificationSpec.sender = c.getString(c.getColumnIndex("senderAddress")); - notificationSpec.subject = c.getString(c.getColumnIndex("subject")); - notificationSpec.body = c.getString(c.getColumnIndex("preview")); - break; - } - } - } - } catch (Exception e) { - e.printStackTrace(); - notificationSpec.sender = "Gadgetbridge"; - notificationSpec.subject = "Permission Error?"; - notificationSpec.body = "Please reinstall Gadgetbridge to enable K-9 Mail notifications"; - } - - GBApplication.deviceService().onNotification(notificationSpec); - } -} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java index a47c8f4d3..5afa67911 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java @@ -3,6 +3,7 @@ package nodomain.freeyourgadget.gadgetbridge.externalevents; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.os.Bundle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -14,7 +15,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; public class MusicPlaybackReceiver extends BroadcastReceiver { private static final Logger LOG = LoggerFactory.getLogger(MusicPlaybackReceiver.class); private static MusicSpec lastMusicSpec = new MusicSpec(); - private static MusicStateSpec lastStatecSpec = new MusicStateSpec(); + private static MusicStateSpec lastStateSpec = new MusicStateSpec(); @Override public void onReceive(Context context, Intent intent) { @@ -26,37 +27,68 @@ public class MusicPlaybackReceiver extends BroadcastReceiver { value != null ? value.toString() : "null", value != null ? value.getClass().getName() : "no class")); } */ - MusicSpec musicSpec = new MusicSpec(); - musicSpec.artist = intent.getStringExtra("artist"); - musicSpec.album = intent.getStringExtra("album"); - if (intent.hasExtra("track")) { - musicSpec.track = intent.getStringExtra("track"); - } - else if (intent.hasExtra("title")) { - musicSpec.track = intent.getStringExtra("title"); - } + MusicSpec musicSpec = new MusicSpec(lastMusicSpec); + MusicStateSpec stateSpec = new MusicStateSpec(lastStateSpec); - musicSpec.duration = intent.getIntExtra("duration", 0) / 1000; + Bundle incomingBundle = intent.getExtras(); + for (String key : incomingBundle.keySet()) { + Object incoming = incomingBundle.get(key); + if (incoming instanceof String && "artist".equals(key)) { + musicSpec.artist = (String) incoming; + } else if (incoming instanceof String && "album".equals(key)) { + musicSpec.album = (String) incoming; + } else if (incoming instanceof String && "track".equals(key)) { + musicSpec.track = (String) incoming; + } else if (incoming instanceof String && "title".equals(key) && musicSpec.track == null) { + musicSpec.track = (String) incoming; + } else if (incoming instanceof Integer && "duration".equals(key)) { + musicSpec.duration = (Integer) incoming / 1000; + } else if (incoming instanceof Long && "duration".equals(key)) { + musicSpec.duration = ((Long) incoming).intValue() / 1000; + } else if (incoming instanceof Integer && "position".equals(key)) { + stateSpec.position = (Integer) incoming / 1000; + } else if (incoming instanceof Long && "position".equals(key)) { + stateSpec.position = ((Long) incoming).intValue() / 1000; + } else if (incoming instanceof Boolean && "playing".equals(key)) { + stateSpec.state = (byte) (((Boolean) incoming) ? MusicStateSpec.STATE_PLAYING : MusicStateSpec.STATE_PAUSED); + stateSpec.playRate = (byte) (((Boolean) incoming) ? 100 : 0); + } else if (incoming instanceof String && "duration".equals(key)) { + musicSpec.duration = Integer.valueOf((String) incoming) / 1000; + } else if (incoming instanceof String && "trackno".equals(key)) { + musicSpec.trackNr = Integer.valueOf((String) incoming); + } else if (incoming instanceof String && "totaltrack".equals(key)) { + musicSpec.trackCount = Integer.valueOf((String) incoming); + } else if (incoming instanceof Integer && "pos".equals(key)) { + stateSpec.position = (Integer) incoming; + } else if (incoming instanceof Integer && "repeat".equals(key)) { + if ((Integer) incoming > 0) { + stateSpec.repeat = 1; + } else { + stateSpec.repeat = 0; + } + } else if (incoming instanceof Integer && "shuffle".equals(key)) { + if ((Integer) incoming > 0) { + stateSpec.shuffle = 1; + } else { + stateSpec.shuffle = 0; + } + } + } if (!lastMusicSpec.equals(musicSpec)) { lastMusicSpec = musicSpec; LOG.info("Update Music Info: " + musicSpec.artist + " / " + musicSpec.album + " / " + musicSpec.track); GBApplication.deviceService().onSetMusicInfo(musicSpec); } else { - LOG.info("got metadata changed intent, but nothing changed, ignoring."); + LOG.info("Got metadata changed intent, but nothing changed, ignoring."); } - if (intent.hasExtra("position") && intent.hasExtra("playing")) { - MusicStateSpec stateSpec = new MusicStateSpec(); - stateSpec.position = intent.getIntExtra("position", 0) / 1000; - stateSpec.state = (byte) (intent.getBooleanExtra("playing", true) ? MusicStateSpec.STATE_PLAYING : MusicStateSpec.STATE_PAUSED); - if (!lastStatecSpec.equals(stateSpec)) { - LOG.info("Update Music State: state=" + stateSpec.state + ", position= " + stateSpec.position); - GBApplication.deviceService().onSetMusicState(stateSpec); - } else { - LOG.info("got state changed intent, but not enough has changed, ignoring."); - } - lastStatecSpec = stateSpec; + if (!lastStateSpec.equals(stateSpec)) { + lastStateSpec = stateSpec; + LOG.info("Update Music State: state=" + stateSpec.state + ", position= " + stateSpec.position); + GBApplication.deviceService().onSetMusicState(stateSpec); + } else { + LOG.info("Got state changed intent, but not enough has changed, ignoring."); } } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java index 745b0b8f7..fc6ecfc64 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java @@ -214,12 +214,6 @@ public class NotificationListener extends NotificationListenerService { return; } - if (source.equals("com.fsck.k9")) { - if (!"never".equals(prefs.getString("notification_mode_k9mail", "when_screen_off"))) { - return; - } - } - if (source.equals("com.moez.QKSMS") || source.equals("com.android.mms") || source.equals("com.sonyericsson.conversations") || @@ -355,51 +349,51 @@ public class NotificationListener extends NotificationListenerService { MediaController c; try { c = new MediaController(getApplicationContext(), (MediaSession.Token) extras.get(Notification.EXTRA_MEDIA_SESSION)); + + PlaybackState s = c.getPlaybackState(); + stateSpec.position = (int) (s.getPosition() / 1000); + stateSpec.playRate = Math.round(100 * s.getPlaybackSpeed()); + stateSpec.repeat = 1; + stateSpec.shuffle = 1; + switch (s.getState()) { + case PlaybackState.STATE_PLAYING: + stateSpec.state = MusicStateSpec.STATE_PLAYING; + break; + case PlaybackState.STATE_STOPPED: + stateSpec.state = MusicStateSpec.STATE_STOPPED; + break; + case PlaybackState.STATE_PAUSED: + stateSpec.state = MusicStateSpec.STATE_PAUSED; + break; + default: + stateSpec.state = MusicStateSpec.STATE_UNKNOWN; + break; + } + + MediaMetadata d = c.getMetadata(); + if (d == null) + return false; + if (d.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) + musicSpec.artist = d.getString(MediaMetadata.METADATA_KEY_ARTIST); + if (d.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) + musicSpec.album = d.getString(MediaMetadata.METADATA_KEY_ALBUM); + if (d.containsKey(MediaMetadata.METADATA_KEY_TITLE)) + musicSpec.track = d.getString(MediaMetadata.METADATA_KEY_TITLE); + if (d.containsKey(MediaMetadata.METADATA_KEY_DURATION)) + musicSpec.duration = (int) d.getLong(MediaMetadata.METADATA_KEY_DURATION) / 1000; + if (d.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) + musicSpec.trackCount = (int) d.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS); + if (d.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) + musicSpec.trackNr = (int) d.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER); + + // finally, tell the device about it + GBApplication.deviceService().onSetMusicInfo(musicSpec); + GBApplication.deviceService().onSetMusicState(stateSpec); + + return true; } catch (NullPointerException e) { return false; } - - PlaybackState s = c.getPlaybackState(); - stateSpec.position = (int) (s.getPosition() / 1000); - stateSpec.playRate = Math.round(100 * s.getPlaybackSpeed()); - stateSpec.repeat = 1; - stateSpec.shuffle = 1; - switch (s.getState()) { - case PlaybackState.STATE_PLAYING: - stateSpec.state = MusicStateSpec.STATE_PLAYING; - break; - case PlaybackState.STATE_STOPPED: - stateSpec.state = MusicStateSpec.STATE_STOPPED; - break; - case PlaybackState.STATE_PAUSED: - stateSpec.state = MusicStateSpec.STATE_PAUSED; - break; - default: - stateSpec.state = MusicStateSpec.STATE_UNKNOWN; - break; - } - - MediaMetadata d = c.getMetadata(); - if (d == null) - return false; - if (d.containsKey(MediaMetadata.METADATA_KEY_ARTIST)) - musicSpec.artist = d.getString(MediaMetadata.METADATA_KEY_ARTIST); - if (d.containsKey(MediaMetadata.METADATA_KEY_ALBUM)) - musicSpec.album = d.getString(MediaMetadata.METADATA_KEY_ALBUM); - if (d.containsKey(MediaMetadata.METADATA_KEY_TITLE)) - musicSpec.track = d.getString(MediaMetadata.METADATA_KEY_TITLE); - if (d.containsKey(MediaMetadata.METADATA_KEY_DURATION)) - musicSpec.duration = (int)d.getLong(MediaMetadata.METADATA_KEY_DURATION) / 1000; - if (d.containsKey(MediaMetadata.METADATA_KEY_NUM_TRACKS)) - musicSpec.trackCount = (int)d.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS); - if (d.containsKey(MediaMetadata.METADATA_KEY_TRACK_NUMBER)) - musicSpec.trackNr = (int)d.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER); - - // finally, tell the device about it - GBApplication.deviceService().onSetMusicInfo(musicSpec); - GBApplication.deviceService().onSetMusicState(stateSpec); - - return true; } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceCandidate.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceCandidate.java index cec517a64..c6d561149 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceCandidate.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceCandidate.java @@ -127,7 +127,7 @@ public class GBDeviceCandidate implements Parcelable { deviceName = (String) method.invoke(device); } } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ignore) { - LOG.info("Could not get device alias for " + deviceName); + LOG.info("Could not get device alias for " + device.getName()); } if (deviceName == null || deviceName.length() == 0) { deviceName = device.getName(); @@ -167,6 +167,6 @@ public class GBDeviceCandidate implements Parcelable { @Override public String toString() { - return getName() + ": " + getMacAddress(); + return getName() + ": " + getMacAddress() + " (" + getDeviceType() + ")"; } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java index 6f217b5d1..05bec9de9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java @@ -3,12 +3,16 @@ package nodomain.freeyourgadget.gadgetbridge.impl; import android.app.Service; import android.content.Context; import android.content.Intent; +import android.database.Cursor; import android.net.Uri; +import android.provider.ContactsContract; import android.support.annotation.Nullable; import java.util.ArrayList; import java.util.UUID; +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; @@ -21,6 +25,8 @@ import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService; import nodomain.freeyourgadget.gadgetbridge.util.LanguageUtils; +import static nodomain.freeyourgadget.gadgetbridge.util.JavaExtensions.coalesce; + public class GBDeviceService implements DeviceService { protected final Context mContext; private final Class mServiceClass; @@ -32,6 +38,7 @@ public class GBDeviceService implements DeviceService { EXTRA_NOTIFICATION_BODY, EXTRA_NOTIFICATION_SOURCENAME, EXTRA_CALL_PHONENUMBER, + EXTRA_CALL_DISPLAYNAME, EXTRA_MUSIC_ARTIST, EXTRA_MUSIC_ALBUM, EXTRA_MUSIC_TRACK, @@ -80,7 +87,7 @@ public class GBDeviceService implements DeviceService { connect(device, false); } - @Override + @Override public void connect(@Nullable GBDevice device, boolean performPair) { Intent intent = createIntent().setAction(ACTION_CONNECT) .putExtra(GBDevice.EXTRA_DEVICE, device) @@ -111,7 +118,7 @@ public class GBDeviceService implements DeviceService { Intent intent = createIntent().setAction(ACTION_NOTIFICATION) .putExtra(EXTRA_NOTIFICATION_FLAGS, notificationSpec.flags) .putExtra(EXTRA_NOTIFICATION_PHONENUMBER, notificationSpec.phoneNumber) - .putExtra(EXTRA_NOTIFICATION_SENDER, notificationSpec.sender) + .putExtra(EXTRA_NOTIFICATION_SENDER, coalesce(notificationSpec.sender, getContactDisplayNameByNumber(notificationSpec.phoneNumber))) .putExtra(EXTRA_NOTIFICATION_SUBJECT, notificationSpec.subject) .putExtra(EXTRA_NOTIFICATION_TITLE, notificationSpec.title) .putExtra(EXTRA_NOTIFICATION_BODY, notificationSpec.body) @@ -144,9 +151,22 @@ public class GBDeviceService implements DeviceService { @Override public void onSetCallState(CallSpec callSpec) { - // name is actually ignored and provided by the service itself... + Context context = GBApplication.getContext(); + String currentPrivacyMode = GBApplication.getPrefs().getString("pref_call_privacy_mode", GBApplication.getContext().getString(R.string.p_call_privacy_mode_off)); + if (context.getString(R.string.p_call_privacy_mode_name).equals(currentPrivacyMode)) { + callSpec.name = callSpec.number; + } + else if (context.getString(R.string.p_call_privacy_mode_complete).equals(currentPrivacyMode)) { + callSpec.number = null; + callSpec.name = null; + } + else { + callSpec.name = coalesce(callSpec.name, getContactDisplayNameByNumber(callSpec.number)); + } + Intent intent = createIntent().setAction(ACTION_CALLSTATE) .putExtra(EXTRA_CALL_PHONENUMBER, callSpec.number) + .putExtra(EXTRA_CALL_DISPLAYNAME, callSpec.name) .putExtra(EXTRA_CALL_COMMAND, callSpec.command); invokeService(intent); } @@ -332,4 +352,29 @@ public class GBDeviceService implements DeviceService { .putExtra(EXTRA_WEATHER_TOMORROWCONDITIONCODE, weatherSpec.tomorrowConditionCode); invokeService(intent); } + + /** + * Returns contact DisplayName by call number + * @param number contact number + * @return contact DisplayName, if found it + */ + private String getContactDisplayNameByNumber(String number) { + Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); + String name = number; + + if (number == null || number.equals("")) { + return name; + } + + try (Cursor contactLookup = mContext.getContentResolver().query(uri, null, null, null, null)) { + if (contactLookup != null && contactLookup.getCount() > 0) { + contactLookup.moveToNext(); + name = contactLookup.getString(contactLookup.getColumnIndex(ContactsContract.Data.DISPLAY_NAME)); + } + } catch (SecurityException e) { + // ignore, just return name below + } + + return name; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java index a7be20f89..9177b077b 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java @@ -60,6 +60,7 @@ public interface DeviceService extends EventHandler { String EXTRA_VIBRATION_INTENSITY = "vibration_intensity"; String EXTRA_CALL_COMMAND = "call_command"; String EXTRA_CALL_PHONENUMBER = "call_phonenumber"; + String EXTRA_CALL_DISPLAYNAME = "call_displayname"; String EXTRA_CANNEDMESSAGES = "cannedmessages"; String EXTRA_CANNEDMESSAGES_TYPE = "cannedmessages_type"; String EXTRA_MUSIC_ARTIST = "music_artist"; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java index aa9f67ca9..9866a935d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java @@ -17,6 +17,19 @@ public class MusicSpec { public int trackCount; public int trackNr; + public MusicSpec() { + + } + + public MusicSpec(MusicSpec old) { + this.duration = old.duration; + this.trackCount = old.trackCount; + this.trackNr = old.trackNr; + this.track = old.track; + this.album = old.album; + this.artist = old.artist; + } + @Override public boolean equals(Object obj) { if (obj == this) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java index fb15948d7..eac51d930 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicStateSpec.java @@ -10,11 +10,23 @@ public class MusicStateSpec { public static final int STATE_UNKNOWN = 3; public byte state; - public int position; - public int playRate; + public int position; // Position of the current media in seconds + public int playRate; // Speed of playback, usually 0 or 100 (full speed) public byte shuffle; public byte repeat; + public MusicStateSpec() { + + } + + public MusicStateSpec(MusicStateSpec old) { + this.state = old.state; + this.position = old.position; + this.playRate = old.playRate; + this.shuffle = old.shuffle; + this.repeat = old.repeat; + } + @Override public boolean equals(Object obj) { if (obj == this) { @@ -31,4 +43,15 @@ public class MusicStateSpec { this.shuffle == stateSpec.shuffle && this.repeat == stateSpec.repeat; } + + @Override + public int hashCode() { + int result = (int) state; +//ignore the position -- it is taken into account in equals() +//result = 31 * result + position; + result = 31 * result + playRate; + result = 31 * result + (int) shuffle; + result = 31 * result + (int) repeat; + return result; + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java index daae8f4ce..2f0128259 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/NotificationType.java @@ -17,7 +17,8 @@ public enum NotificationType { SIGNAL(PebbleIconID.NOTIFICATION_HIPCHAT, PebbleColor.BlueMoon), TWITTER(PebbleIconID.NOTIFICATION_TWITTER, PebbleColor.BlueMoon), TELEGRAM(PebbleIconID.NOTIFICATION_TELEGRAM, PebbleColor.PictonBlue), - WHATSAPP(PebbleIconID.NOTIFICATION_WHATSAPP, PebbleColor.MayGreen); + WHATSAPP(PebbleIconID.NOTIFICATION_WHATSAPP, PebbleColor.MayGreen), + GENERIC_ALARM_CLOCK(PebbleIconID.ALARM_CLOCK, PebbleColor.Red); public int icon; public byte color; @@ -41,6 +42,7 @@ public enum NotificationType { case GENERIC_EMAIL: case GENERIC_NAVIGATION: case GENERIC_SMS: + case GENERIC_ALARM_CLOCK: return getFixedValue(); case FACEBOOK: case TWITTER: diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java index 336e0a927..cae037f72 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java @@ -8,10 +8,8 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; -import android.database.Cursor; import android.net.Uri; import android.os.IBinder; -import android.provider.ContactsContract; import android.support.annotation.Nullable; import android.support.v4.content.LocalBroadcastManager; import android.widget.Toast; @@ -25,13 +23,12 @@ import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; -import nodomain.freeyourgadget.gadgetbridge.activities.OnboardingActivity; import nodomain.freeyourgadget.gadgetbridge.database.DBHandler; import nodomain.freeyourgadget.gadgetbridge.database.DBHelper; import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmClockReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.AlarmReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.BluetoothConnectReceiver; -import nodomain.freeyourgadget.gadgetbridge.externalevents.K9Receiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.MusicPlaybackReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.PebbleReceiver; import nodomain.freeyourgadget.gadgetbridge.externalevents.PhoneCallReceiver; @@ -42,7 +39,6 @@ import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; -import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; @@ -97,6 +93,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CAL import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_TITLE; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALENDAREVENT_TYPE; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_COMMAND; +import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_DISPLAYNAME; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_PHONENUMBER; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CANNEDMESSAGES; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CANNEDMESSAGES_TYPE; @@ -148,15 +145,25 @@ public class DeviceCommunicationService extends Service implements SharedPrefere private PhoneCallReceiver mPhoneCallReceiver = null; private SMSReceiver mSMSReceiver = null; - private K9Receiver mK9Receiver = null; private PebbleReceiver mPebbleReceiver = null; private MusicPlaybackReceiver mMusicPlaybackReceiver = null; private TimeChangeReceiver mTimeChangeReceiver = null; private BluetoothConnectReceiver mBlueToothConnectReceiver = null; - private AlarmReceiver mAlarmReceiver = null; + private AlarmClockReceiver mAlarmClockReceiver = null; + private AlarmReceiver mAlarmReceiver = null; private Random mRandom = new Random(); + private final String[] mMusicActions = { + "com.android.music.metachanged", + "com.android.music.playstatechanged", + "com.android.music.queuechanged", + "com.android.music.playbackcomplete", + "net.sourceforge.subsonic.androidapp.EVENT_META_CHANGED", + "com.maxmpz.audioplayer.TPOS_SYNC", + "com.maxmpz.audioplayer.STATUS_CHANGED", + "com.maxmpz.audioplayer.PLAYING_MODE_CHANGED"}; + /** * For testing! * @@ -186,20 +193,7 @@ public class DeviceCommunicationService extends Service implements SharedPrefere if (device.isInitialized()) { try (DBHandler dbHandler = GBApplication.acquireDB()) { DaoSession session = dbHandler.getDaoSession(); - boolean askForDBMigration = false; - if (DBHelper.findDevice(device, session) == null && device.getType() != DeviceType.VIBRATISSIMO && (device.getType() != DeviceType.LIVEVIEW)) { - askForDBMigration = true; - } DBHelper.getDevice(device, session); // implicitly creates the device in database if not present, and updates device attributes - if (askForDBMigration) { - DBHelper dbHelper = new DBHelper(context); - if (dbHelper.getOldActivityDatabaseHandler() != null) { - Intent startIntent = new Intent(context, OnboardingActivity.class); - startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startIntent.putExtra(GBDevice.EXTRA_DEVICE, device); - startActivity(startIntent); - } - } } catch (Exception ignore) { } } @@ -332,8 +326,6 @@ public class DeviceCommunicationService extends Service implements SharedPrefere notificationSpec.flags = intent.getIntExtra(EXTRA_NOTIFICATION_FLAGS, 0); if (notificationSpec.type == NotificationType.GENERIC_SMS && notificationSpec.phoneNumber != null) { - notificationSpec.sender = getContactDisplayNameByNumber(notificationSpec.phoneNumber); - notificationSpec.id = mRandom.nextInt(); // FIXME: add this in external SMS Receiver? GBApplication.getIDSenderLookup().add(notificationSpec.id, notificationSpec.phoneNumber); } @@ -412,18 +404,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere break; } case ACTION_CALLSTATE: - int command = intent.getIntExtra(EXTRA_CALL_COMMAND, CallSpec.CALL_UNDEFINED); - - String phoneNumber = intent.getStringExtra(EXTRA_CALL_PHONENUMBER); - String callerName = null; - if (phoneNumber != null) { - callerName = getContactDisplayNameByNumber(phoneNumber); - } - CallSpec callSpec = new CallSpec(); - callSpec.command = command; - callSpec.number = phoneNumber; - callSpec.name = callerName; + callSpec.command = intent.getIntExtra(EXTRA_CALL_COMMAND, CallSpec.CALL_UNDEFINED); + callSpec.number = intent.getStringExtra(EXTRA_CALL_PHONENUMBER); + callSpec.name = intent.getStringExtra(EXTRA_CALL_DISPLAYNAME); mDeviceSupport.onSetCallState(callSpec); break; case ACTION_SETCANNEDMESSAGES: @@ -595,13 +579,6 @@ public class DeviceCommunicationService extends Service implements SharedPrefere mSMSReceiver = new SMSReceiver(); registerReceiver(mSMSReceiver, new IntentFilter("android.provider.Telephony.SMS_RECEIVED")); } - if (mK9Receiver == null) { - mK9Receiver = new K9Receiver(); - IntentFilter filter = new IntentFilter(); - filter.addDataScheme("email"); - filter.addAction("com.fsck.k9.intent.action.EMAIL_RECEIVED"); - registerReceiver(mK9Receiver, filter); - } if (mPebbleReceiver == null) { mPebbleReceiver = new PebbleReceiver(); registerReceiver(mPebbleReceiver, new IntentFilter("com.getpebble.action.SEND_NOTIFICATION")); @@ -609,9 +586,9 @@ public class DeviceCommunicationService extends Service implements SharedPrefere if (mMusicPlaybackReceiver == null) { mMusicPlaybackReceiver = new MusicPlaybackReceiver(); IntentFilter filter = new IntentFilter(); - filter.addAction("com.android.music.metachanged"); - filter.addAction("net.sourceforge.subsonic.androidapp.EVENT_META_CHANGED"); - //filter.addAction("com.android.music.playstatechanged"); + for (String action : mMusicActions){ + filter.addAction(action); + } registerReceiver(mMusicPlaybackReceiver, filter); } if (mTimeChangeReceiver == null) { @@ -629,6 +606,13 @@ public class DeviceCommunicationService extends Service implements SharedPrefere mAlarmReceiver = new AlarmReceiver(); registerReceiver(mAlarmReceiver, new IntentFilter("DAILY_ALARM")); } + if (mAlarmClockReceiver == null) { + mAlarmClockReceiver = new AlarmClockReceiver(); + IntentFilter filter = new IntentFilter(); + filter.addAction(AlarmClockReceiver.ALARM_ALERT_ACTION); + filter.addAction(AlarmClockReceiver.ALARM_DONE_ACTION); + registerReceiver(mAlarmClockReceiver, filter); + } } else { if (mPhoneCallReceiver != null) { unregisterReceiver(mPhoneCallReceiver); @@ -638,10 +622,6 @@ public class DeviceCommunicationService extends Service implements SharedPrefere unregisterReceiver(mSMSReceiver); mSMSReceiver = null; } - if (mK9Receiver != null) { - unregisterReceiver(mK9Receiver); - mK9Receiver = null; - } if (mPebbleReceiver != null) { unregisterReceiver(mPebbleReceiver); mPebbleReceiver = null; @@ -662,6 +642,10 @@ public class DeviceCommunicationService extends Service implements SharedPrefere unregisterReceiver(mAlarmReceiver); mAlarmReceiver = null; } + if (mAlarmClockReceiver != null) { + unregisterReceiver(mAlarmClockReceiver); + mAlarmClockReceiver = null; + } } } @@ -687,27 +671,6 @@ public class DeviceCommunicationService extends Service implements SharedPrefere return null; } - - private String getContactDisplayNameByNumber(String number) { - Uri uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); - String name = number; - - if (number == null || number.equals("")) { - return name; - } - - try (Cursor contactLookup = getContentResolver().query(uri, null, null, null, null)) { - if (contactLookup != null && contactLookup.getCount() > 0) { - contactLookup.moveToNext(); - name = contactLookup.getString(contactLookup.getColumnIndex(ContactsContract.Data.DISPLAY_NAME)); - } - } catch (SecurityException e) { - // ignore, just return name below - } - - return name; - } - @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if (GBPrefs.AUTO_RECONNECT.equals(key)) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java index 4bf02c551..cedd16f6d 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/hplus/HPlusSupport.java @@ -26,6 +26,7 @@ import java.util.GregorianCalendar; import java.util.List; import java.util.UUID; +import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusConstants; import nodomain.freeyourgadget.gadgetbridge.devices.hplus.HPlusCoordinator; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -109,11 +110,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { //Initialize device sendUserInfo(builder); //Sync preferences - setSIT(builder); //Sync SIT Interval - setCurrentDate(builder); // Sync Current Date - setDayOfWeek(builder); - setCurrentTime(builder); // Sync Current Time - setLanguage(builder); + requestDeviceInfo(builder); @@ -141,68 +138,23 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { private HPlusSupport syncPreferences(TransactionBuilder transaction) { if(deviceType == DeviceType.HPLUS) { - byte gender = HPlusCoordinator.getUserGender(getDevice().getAddress()); - byte age = HPlusCoordinator.getUserAge(getDevice().getAddress()); - byte bodyHeight = HPlusCoordinator.getUserHeight(getDevice().getAddress()); - byte bodyWeight = HPlusCoordinator.getUserWeight(getDevice().getAddress()); - int goal = HPlusCoordinator.getGoal(getDevice().getAddress()); - byte displayTime = HPlusCoordinator.getScreenTime(getDevice().getAddress()); - byte country = HPlusCoordinator.getLanguage(getDevice().getAddress()); - byte social = HPlusCoordinator.getSocial(getDevice().getAddress()); // ?? - byte allDayHeart = HPlusCoordinator.getAllDayHR(getDevice().getAddress()); - byte wrist = HPlusCoordinator.getUserWrist(getDevice().getAddress()); - byte alertTimeHour = 0; - byte alertTimeMinute = 0; - - if (HPlusCoordinator.getSWAlertTime(getDevice().getAddress())) { - int t = HPlusCoordinator.getAlertTime(getDevice().getAddress()); - - alertTimeHour = (byte) ((t / 256) & 0xff); - alertTimeMinute = (byte) (t % 256); - } - - byte unit = HPlusCoordinator.getUnit(getDevice().getAddress()); - byte timemode = HPlusCoordinator.getTimeMode((getDevice().getAddress())); - - transaction.write(ctrlCharacteristic, new byte[]{ - HPlusConstants.CMD_SET_PREFS, - gender, - age, - bodyHeight, - bodyWeight, - 0, - 0, - (byte) ((goal / 256) & 0xff), - (byte) (goal % 256), - displayTime, - country, - 0, - social, - allDayHeart, - wrist, - 0, - alertTimeHour, - alertTimeMinute, - unit, - timemode - }); - - }else if(deviceType == DeviceType.MAKIBESF68){ - //Makibes doesn't support setting everything at once. - - setGender(transaction); - setAge(transaction); - setWeight(transaction); - setHeight(transaction); - setGoal(transaction); - setLanguage(transaction); - setScreenTime(transaction); - //setAlarm(transaction, t); - setUnit(transaction); - setTimeMode(transaction); + setSIT(transaction); //Sync SIT Interval } + setCurrentDate(transaction); + setCurrentTime(transaction); + setDayOfWeek(transaction); + setTimeMode(transaction); + setGender(transaction); + setAge(transaction); + setWeight(transaction); + setHeight(transaction); + + setGoal(transaction); + setLanguage(transaction); + setScreenTime(transaction); + setUnit(transaction); setAllDayHeart(transaction); return this; @@ -220,6 +172,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { private HPlusSupport setTimeMode(TransactionBuilder transaction) { byte value = HPlusCoordinator.getTimeMode(getDevice().getAddress()); + transaction.write(ctrlCharacteristic, new byte[]{ HPlusConstants.CMD_SET_TIMEMODE, value @@ -381,15 +334,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { private HPlusSupport setAllDayHeart(TransactionBuilder transaction) { - byte value = HPlusCoordinator.getHRState(getDevice().getAddress()); - - transaction.write(ctrlCharacteristic, new byte[]{ - HPlusConstants.CMD_SET_HEARTRATE_STATE, - value - }); - - - value = HPlusCoordinator.getAllDayHR(getDevice().getAddress()); + byte value = HPlusCoordinator.getAllDayHR(getDevice().getAddress()); transaction.write(ctrlCharacteristic, new byte[]{ HPlusConstants.CMD_SET_ALLDAY_HRM, @@ -403,13 +348,17 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { private HPlusSupport setAlarm(TransactionBuilder transaction, Calendar t) { + byte hour = HPlusConstants.ARG_ALARM_DISABLE; + byte minute = HPlusConstants.ARG_ALARM_DISABLE; + + if(t != null){ + hour = (byte) t.get(Calendar.HOUR_OF_DAY); + minute = (byte) t.get(Calendar.MINUTE); + } + transaction.write(ctrlCharacteristic, new byte[]{HPlusConstants.CMD_SET_ALARM, - (byte) (t.get(Calendar.YEAR) / 256), - (byte) (t.get(Calendar.YEAR) % 256), - (byte) (t.get(Calendar.MONTH) + 1), - (byte) t.get(Calendar.HOUR_OF_DAY), - (byte) t.get(Calendar.MINUTE), - (byte) t.get(Calendar.SECOND)}); + hour, + minute}); return this; } @@ -481,8 +430,8 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { @Override public void onSetAlarms(ArrayList alarms) { - if (alarms.size() == 0) - return; + + TransactionBuilder builder = new TransactionBuilder("alarm"); for (Alarm alarm : alarms) { @@ -493,13 +442,19 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { continue; Calendar t = alarm.getAlarmCal(); - TransactionBuilder builder = new TransactionBuilder("alarm"); setAlarm(builder, t); builder.queue(getQueue()); + GB.toast(getContext(), getContext().getString(R.string.user_feedback_miband_set_alarms_ok), Toast.LENGTH_SHORT, GB.INFO); + return; //Only first alarm } + setAlarm(builder, null); + builder.queue(getQueue()); + + GB.toast(getContext(), getContext().getString(R.string.user_feedback_all_alarms_disabled), Toast.LENGTH_SHORT, GB.INFO); + } @Override @@ -530,7 +485,7 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport { @Override public void onEnableRealtimeSteps(boolean enable) { - + onEnableRealtimeHeartRateMeasurement(enable); } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java index be1e32fb2..e5ef862a8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java @@ -50,6 +50,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.DeviceService; import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationType; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction; @@ -102,6 +103,8 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo(); private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo(); private RealtimeSamplesSupport realtimeSamplesSupport; + private boolean alarmClockRining; + private boolean alarmClockRinging; public MiBandSupport() { super(LOG); @@ -541,13 +544,29 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { @Override public void onNotification(NotificationSpec notificationSpec) { + if (notificationSpec.type == NotificationType.GENERIC_ALARM_CLOCK) { + onAlarmClock(notificationSpec); + return; + } + String origin = notificationSpec.type.getGenericType(); performPreferredNotification(origin + " received", origin, null); } + private void onAlarmClock(NotificationSpec notificationSpec) { + alarmClockRining = true; + AbortTransactionAction abortAction = new AbortTransactionAction() { + @Override + protected boolean shouldAbort() { + return !isAlarmClockRinging(); + } + }; + performPreferredNotification("alarm clock ringing", MiBandConst.ORIGIN_ALARM_CLOCK, abortAction); + } + @Override public void onDeleteNotification(int id) { - + alarmClockRining = false; // we should have the notificationtype at least to check } @Override @@ -616,6 +635,10 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport { public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) { } + private boolean isAlarmClockRinging() { + // don't synchronize, this is not really important + return alarmClockRinging; + } private boolean isTelephoneRinging() { // don't synchronize, this is not really important return telephoneRinging; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java index d068c2c55..6d9a83fcf 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/MiBand2Support.java @@ -73,6 +73,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.CheckAuthenti import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.DeviceInfo; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.NotificationStrategy; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.RealtimeSamplesSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.actions.StopNotificationAction; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.FetchActivityOperation; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.InitOperation; import nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.operations.UpdateFirmwareOperation; @@ -123,6 +124,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo(); private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo(); private RealtimeSamplesSupport realtimeSamplesSupport; + private boolean alarmClockRinging; public MiBand2Support() { super(LOG); @@ -271,6 +273,7 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { // .notify(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_SENSOR_DATA), enable); builder.notify(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_3_CONFIGURATION), enable); builder.notify(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_6_BATTERY_INFO), enable); + builder.notify(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_10_BUTTON), enable); BluetoothGattCharacteristic heartrateCharacteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT); if (heartrateCharacteristic != null) { builder.notify(heartrateCharacteristic, enable); @@ -586,6 +589,10 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { @Override public void onNotification(NotificationSpec notificationSpec) { + if (notificationSpec.type == NotificationType.GENERIC_ALARM_CLOCK) { + onAlarmClock(notificationSpec); + return; + } int alertLevel = MiBand2Service.ALERT_LEVEL_MESSAGE; if (notificationSpec.type == NotificationType.UNKNOWN) { alertLevel = MiBand2Service.ALERT_LEVEL_VIBRATE_ONLY; @@ -594,9 +601,20 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { performPreferredNotification(origin + " received", origin, alertLevel, null); } + private void onAlarmClock(NotificationSpec notificationSpec) { + alarmClockRinging = true; + AbortTransactionAction abortAction = new StopNotificationAction(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL)) { + @Override + protected boolean shouldAbort() { + return !isAlarmClockRinging(); + } + }; + performPreferredNotification("alarm clock ringing", MiBandConst.ORIGIN_ALARM_CLOCK, MiBand2Service.ALERT_LEVEL_VIBRATE_ONLY, abortAction); + } + @Override public void onDeleteNotification(int id) { - + alarmClockRinging = false; // we should have the notificationtype at least to check } @Override @@ -616,23 +634,11 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { public void onSetCallState(CallSpec callSpec) { if (callSpec.command == CallSpec.CALL_INCOMING) { telephoneRinging = true; - AbortTransactionAction abortAction = new AbortTransactionAction() { + AbortTransactionAction abortAction = new StopNotificationAction(getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL)) { @Override protected boolean shouldAbort() { return !isTelephoneRinging(); } - - @Override - public boolean run(BluetoothGatt gatt) { - if (!super.run(gatt)) { - // send a signal to stop the vibration - BluetoothGattCharacteristic characteristic = MiBand2Support.this.getCharacteristic(GattCharacteristic.UUID_CHARACTERISTIC_ALERT_LEVEL); - characteristic.setValue(new byte[] {MiBand2Service.ALERT_LEVEL_NONE}); - gatt.writeCharacteristic(characteristic); - return false; - } - return true; - } }; performPreferredNotification("incoming call", MiBandConst.ORIGIN_INCOMING_CALL, MiBand2Service.ALERT_LEVEL_PHONE_CALL, abortAction); } else if ((callSpec.command == CallSpec.CALL_START) || (callSpec.command == CallSpec.CALL_END)) { @@ -644,6 +650,11 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) { } + private boolean isAlarmClockRinging() { + // don't synchronize, this is not really important + return alarmClockRinging; + } + private boolean isTelephoneRinging() { // don't synchronize, this is not really important return telephoneRinging; @@ -848,6 +859,9 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { LOG.info("AUTHENTICATION?? " + characteristicUUID); logMessageContent(characteristic.getValue()); return true; + } else if (MiBand2Service.UUID_CHARACTERISTIC_10_BUTTON.equals(characteristicUUID)) { + handleButtonPressed(characteristic.getValue()); + return true; } else { LOG.info("Unhandled characteristic changed: " + characteristicUUID); logMessageContent(characteristic.getValue()); @@ -855,6 +869,11 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { return false; } + private void handleButtonPressed(byte[] value) { + LOG.info("Button pressed: " + value); + logMessageContent(value); + } + private void handleUnknownCharacteristic(byte[] value) { } @@ -877,6 +896,9 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { } else if (MiBandService.UUID_CHARACTERISTIC_DATE_TIME.equals(characteristicUUID)) { logDate(characteristic.getValue(), status); return true; + } else if (MiBand2Service.UUID_CHARACTERISTIC_10_BUTTON.equals(characteristicUUID)) { + handleButtonPressed(characteristic.getValue()); + return true; } else { LOG.info("Unhandled characteristic read: " + characteristicUUID); logMessageContent(characteristic.getValue()); @@ -1250,6 +1272,13 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport { @Override public void onTestNewFunction() { + try { + performInitialized("read characteristic 10") + .read(getCharacteristic(MiBand2Service.UUID_CHARACTERISTIC_10_BUTTON)) + .queue(getQueue()); + } catch (IOException e) { + e.printStackTrace(); + } } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/actions/StopNotificationAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/actions/StopNotificationAction.java new file mode 100644 index 000000000..1de8ad6cc --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband2/actions/StopNotificationAction.java @@ -0,0 +1,28 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.miband2.actions; + +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; + +import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBand2Service; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction; + +public abstract class StopNotificationAction extends AbortTransactionAction { + + private final BluetoothGattCharacteristic alertLevelCharacteristic; + + public StopNotificationAction(BluetoothGattCharacteristic alertLevelCharacteristic) { + this.alertLevelCharacteristic = alertLevelCharacteristic; + } + + @Override + public boolean run(BluetoothGatt gatt) { + if (!super.run(gatt)) { + // send a signal to stop the vibration + alertLevelCharacteristic.setValue(new byte[]{MiBand2Service.ALERT_LEVEL_NONE}); + gatt.writeCharacteristic(alertLevelCharacteristic); + return false; + } + return true; + } +}; + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerObsidian.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerObsidian.java new file mode 100644 index 000000000..0f5f783b0 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/AppMessageHandlerObsidian.java @@ -0,0 +1,160 @@ +package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; + +import android.util.Pair; +import android.widget.Toast; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; +import nodomain.freeyourgadget.gadgetbridge.model.Weather; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; +import nodomain.freeyourgadget.gadgetbridge.util.GB; + +class AppMessageHandlerObsidian extends AppMessageHandler { + + /* + "appKeys": { + "CONFIG_WEATHER_REFRESH": 35, + "CONFIG_WEATHER_UNIT_LOCAL": 31, + "MSG_KEY_WEATHER_TEMP": 100, + + "CONFIG_WEATHER_EXPIRATION": 36, + "MSG_KEY_FETCH_WEATHER": 102, + "MSG_KEY_WEATHER_ICON": 101, + "MSG_KEY_WEATHER_FAILED": 104, + "CONFIG_WEATHER_MODE_LOCAL": 30, + "CONFIG_WEATHER_APIKEY_LOCAL": 33, + "CONFIG_WEATHER_LOCAL": 28, + "CONFIG_COLOR_WEATHER": 29, + "CONFIG_WEATHER_LOCATION_LOCAL": 34, + "CONFIG_WEATHER_SOURCE_LOCAL": 32 + } + */ + + + private static final String ICON_01d = "a"; //night icons are just uppercase + private static final String ICON_02d = "b"; + private static final String ICON_03d = "c"; + private static final String ICON_04d = "d"; + private static final String ICON_09d = "e"; + private static final String ICON_10d = "f"; + private static final String ICON_11d = "g"; + private static final String ICON_13d = "h"; + private static final String ICON_50d = "i"; + + + AppMessageHandlerObsidian(UUID uuid, PebbleProtocol pebbleProtocol) { + super(uuid, pebbleProtocol); + messageKeys = new HashMap<>(); + try { + JSONObject appKeys = getAppKeys(); + Iterator appKeysIterator = appKeys.keys(); + while (appKeysIterator.hasNext()) { + String current = appKeysIterator.next(); + switch (current) { + case "CONFIG_WEATHER_REFRESH": + case "CONFIG_WEATHER_UNIT_LOCAL": + case "MSG_KEY_WEATHER_TEMP": + case "MSG_KEY_WEATHER_ICON": + messageKeys.put(current, appKeys.getInt(current)); + break; + } + } + } catch (JSONException e) { + GB.toast("There was an error accessing the timestyle watchface configuration.", Toast.LENGTH_LONG, GB.ERROR); + } catch (IOException ignore) { + } + } + + private String getIconForConditionCode(int conditionCode, boolean isNight) { + + int generalCondition = conditionCode / 100; + String iconToLoad; + // determine the correct icon + switch (generalCondition) { + case 2: //thunderstorm + iconToLoad = ICON_11d; + break; + case 3: //drizzle + iconToLoad = ICON_09d; + break; + case 5: //rain + if (conditionCode == 500) { + iconToLoad = ICON_09d; + } else if (conditionCode < 505) { + iconToLoad = ICON_10d; + } else if (conditionCode == 511) { + iconToLoad = ICON_10d; + } else { + iconToLoad = ICON_09d; + } + break; + case 6: //snow + if (conditionCode == 600 || conditionCode == 620) { + iconToLoad = ICON_13d; + } else if (conditionCode > 610 && conditionCode < 620) { + iconToLoad = ICON_13d; + } else { + iconToLoad = ICON_13d; + } + break; + case 7: // fog, dust, etc + iconToLoad = ICON_03d; + break; + case 8: // clouds + if (conditionCode == 800) { + iconToLoad = ICON_01d; + } else if (conditionCode < 803) { + iconToLoad = ICON_02d; + } else { + iconToLoad = ICON_04d; + } + break; + default: + iconToLoad = ICON_02d; + break; + } + + return (!isNight) ? iconToLoad : iconToLoad.toUpperCase(); + } + + private byte[] encodeObisdianWeather(WeatherSpec weatherSpec) { + + if (weatherSpec == null) { + return null; + } + + ArrayList> pairs = new ArrayList<>(); + boolean isNight = false; //TODO: use the night icons when night + pairs.add(new Pair<>(messageKeys.get("CONFIG_WEATHER_REFRESH"), (Object) 60)); + pairs.add(new Pair<>(messageKeys.get("CONFIG_WEATHER_UNIT_LOCAL"), (Object) 1)); //celsius + pairs.add(new Pair<>(messageKeys.get("MSG_KEY_WEATHER_ICON"), (Object) getIconForConditionCode(weatherSpec.currentConditionCode, isNight))); //celsius + pairs.add(new Pair<>(messageKeys.get("MSG_KEY_WEATHER_TEMP"), (Object) (weatherSpec.currentTemp - 273))); + + return mPebbleProtocol.encodeApplicationMessagePush(PebbleProtocol.ENDPOINT_APPLICATIONMESSAGE, mUUID, pairs); + } + + @Override + public GBDeviceEvent[] onAppStart() { + WeatherSpec weatherSpec = Weather.getInstance().getWeatherSpec(); + if (weatherSpec == null) { + return new GBDeviceEvent[]{null}; + } + GBDeviceEventSendBytes sendBytes = new GBDeviceEventSendBytes(); + sendBytes.encodedBytes = encodeObisdianWeather(weatherSpec); + return new GBDeviceEvent[]{sendBytes}; + } + + @Override + public byte[] encodeUpdateWeather(WeatherSpec weatherSpec) { + return encodeObisdianWeather(weatherSpec); + } +} \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSession.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSession.java index 2b07d944a..ed03b054a 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSession.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSession.java @@ -1,20 +1,29 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.nio.ByteBuffer; import java.util.UUID; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.pebble.GBDeviceEventDataLogging; + class DatalogSession { + private static final Logger LOG = LoggerFactory.getLogger(DatalogSession.class); + final byte id; final int tag; final UUID uuid; final byte itemType; final short itemSize; + final int timestamp; String taginfo = "(unknown)"; - DatalogSession(byte id, UUID uuid, int tag, byte itemType, short itemSize) { + DatalogSession(byte id, UUID uuid, int timestamp, int tag, byte itemType, short itemSize) { this.id = id; this.tag = tag; this.uuid = uuid; + this.timestamp = timestamp; this.itemType = itemType; this.itemSize = itemSize; } @@ -26,4 +35,44 @@ class DatalogSession { String getTaginfo() { return taginfo; } + + GBDeviceEventDataLogging handleMessageForPebbleKit(ByteBuffer buf, int length) { + if (0 != (length % itemSize)) { + LOG.warn("invalid length"); + return null; + } + int packetCount = length / itemSize; + + if (packetCount <= 0) { + LOG.warn("invalid number of datalog elements"); + return null; + } + + GBDeviceEventDataLogging dataLogging = new GBDeviceEventDataLogging(); + dataLogging.command = GBDeviceEventDataLogging.COMMAND_RECEIVE_DATA; + dataLogging.appUUID = uuid; + dataLogging.timestamp = timestamp & 0xffffffffL; + dataLogging.tag = tag; + dataLogging.pebbleDataType = itemType; + dataLogging.data = new Object[packetCount]; + + for (int i = 0; i < packetCount; i++) { + switch (itemType) { + case PebbleProtocol.TYPE_BYTEARRAY: + byte[] itemData = new byte[itemSize]; + buf.get(itemData); + dataLogging.data[i] = itemData; + break; + + case PebbleProtocol.TYPE_UINT: + dataLogging.data[i] = buf.getInt() & 0xffffffffL; + break; + + case PebbleProtocol.TYPE_INT: + dataLogging.data[i] = buf.getInt(); + break; + } + } + return dataLogging; + } } \ No newline at end of file diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthHR.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthHR.java index 2f70e1f4c..61400aa49 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthHR.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthHR.java @@ -13,8 +13,8 @@ class DatalogSessionHealthHR extends DatalogSessionPebbleHealth { private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthHR.class); - DatalogSessionHealthHR(byte id, UUID uuid, int tag, byte item_type, short item_size, GBDevice device) { - super(id, uuid, tag, item_type, item_size, device); + DatalogSessionHealthHR(byte id, UUID uuid, int timestamp, int tag, byte item_type, short item_size, GBDevice device) { + super(id, uuid, timestamp, tag, item_type, item_size, device); taginfo = "(Health - HR " + tag + " )"; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java index e41d8f325..463c57493 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthOverlayData.java @@ -22,8 +22,8 @@ class DatalogSessionHealthOverlayData extends DatalogSessionPebbleHealth { private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthOverlayData.class); - public DatalogSessionHealthOverlayData(byte id, UUID uuid, int tag, byte item_type, short item_size, GBDevice device) { - super(id, uuid, tag, item_type, item_size, device); + DatalogSessionHealthOverlayData(byte id, UUID uuid, int timestamp, int tag, byte item_type, short item_size, GBDevice device) { + super(id, uuid, timestamp, tag, item_type, item_size, device); taginfo = "(Health - overlay data " + tag + " )"; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java index d6534ef71..1cb770822 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java @@ -22,8 +22,8 @@ class DatalogSessionHealthSleep extends DatalogSessionPebbleHealth { private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthSleep.class); - public DatalogSessionHealthSleep(byte id, UUID uuid, int tag, byte item_type, short item_size, GBDevice device) { - super(id, uuid, tag, item_type, item_size, device); + DatalogSessionHealthSleep(byte id, UUID uuid, int timestamp, int tag, byte item_type, short item_size, GBDevice device) { + super(id, uuid, timestamp, tag, item_type, item_size, device); taginfo = "(Health - sleep " + tag + " )"; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java index 1f69dcd1c..725c89241 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java @@ -20,8 +20,8 @@ class DatalogSessionHealthSteps extends DatalogSessionPebbleHealth { private static final Logger LOG = LoggerFactory.getLogger(DatalogSessionHealthSteps.class); - public DatalogSessionHealthSteps(byte id, UUID uuid, int tag, byte item_type, short item_size, GBDevice device) { - super(id, uuid, tag, item_type, item_size, device); + DatalogSessionHealthSteps(byte id, UUID uuid, int timestamp, int tag, byte item_type, short item_size, GBDevice device) { + super(id, uuid, timestamp, tag, item_type, item_size, device); taginfo = "(Health - steps)"; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java index 2aae2cb92..e54780aa8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionPebbleHealth.java @@ -10,8 +10,8 @@ abstract class DatalogSessionPebbleHealth extends DatalogSession { private final GBDevice mDevice; - DatalogSessionPebbleHealth(byte id, UUID uuid, int tag, byte itemType, short itemSize, GBDevice device) { - super(id, uuid, tag, itemType, itemSize); + DatalogSessionPebbleHealth(byte id, UUID uuid, int timestamp, int tag, byte itemType, short itemSize, GBDevice device) { + super(id, uuid, timestamp, tag, itemType, itemSize); mDevice = device; } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java index 00abe7556..2a116a451 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java @@ -33,6 +33,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppManagement; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.pebble.GBDeviceEventDataLogging; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PBWReader; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleInstallable; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; @@ -91,6 +92,8 @@ class PebbleIoThread extends GBDeviceIoThread { mBtAdapter = btAdapter; mPebbleSupport = pebbleSupport; mEnablePebblekit = prefs.getBoolean("pebble_enable_pebblekit", false); + mPebbleProtocol.setAlwaysACKPebbleKit(prefs.getBoolean("pebble_always_ack_pebblekit", false)); + mPebbleProtocol.setEnablePebbleKit(mEnablePebblekit); } private int readWithException(InputStream inputStream, byte[] buffer, int byteOffset, int byteCount) throws IOException { @@ -504,6 +507,13 @@ class PebbleIoThread extends GBDeviceIoThread { mPebbleKitSupport.sendAppMessageIntent((GBDeviceEventAppMessage) deviceEvent); } } + } else if (deviceEvent instanceof GBDeviceEventDataLogging) { + if (mEnablePebblekit) { + LOG.info("Got Datalogging event"); + if (mPebbleKitSupport != null) { + mPebbleKitSupport.sendDataLoggingIntent((GBDeviceEventDataLogging) deviceEvent); + } + } } return false; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleKitSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleKitSupport.java index 6d05dc8c2..fbccba860 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleKitSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleKitSupport.java @@ -4,6 +4,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.util.Base64; import org.json.JSONArray; import org.json.JSONException; @@ -13,6 +14,7 @@ import org.slf4j.LoggerFactory; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppMessage; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.pebble.GBDeviceEventDataLogging; class PebbleKitSupport { //private static final String PEBBLEKIT_ACTION_PEBBLE_CONNECTED = "com.getpebble.action.PEBBLE_CONNECTED"; @@ -26,12 +28,20 @@ class PebbleKitSupport { private static final String PEBBLEKIT_ACTION_APP_START = "com.getpebble.action.app.START"; private static final String PEBBLEKIT_ACTION_APP_STOP = "com.getpebble.action.app.STOP"; + private static final String PEBBLEKIT_ACTION_DL_RECEIVE_DATA_NEW = "com.getpebble.action.dl.RECEIVE_DATA_NEW"; + private static final String PEBBLEKIT_ACTION_DL_RECEIVE_DATA = "com.getpebble.action.dl.RECEIVE_DATA"; + private static final String PEBBLEKIT_ACTION_DL_ACK_DATA = "com.getpebble.action.dl.ACK_DATA"; + private static final String PEBBLEKIT_ACTION_DL_REQUEST_DATA = "com.getpebble.action.dl.REQUEST_DATA"; + private static final String PEBBLEKIT_ACTION_DL_FINISH_SESSION = "com.getpebble.action.dl.FINISH_SESSION_NEW"; + private static final Logger LOG = LoggerFactory.getLogger(PebbleKitSupport.class); private final PebbleProtocol mPebbleProtocol; private final Context mContext; private final PebbleIoThread mPebbleIoThread; + private int dataLogTransactionId = 1; + private final BroadcastReceiver mPebbleKitReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -55,21 +65,26 @@ class PebbleKitSupport { try { JSONArray jsonArray = new JSONArray(jsonString); mPebbleIoThread.write(mPebbleProtocol.encodeApplicationMessageFromJSON(uuid, jsonArray)); - if (transaction_id >= 0 && transaction_id <= 255) { - sendAppMessageAck(transaction_id); - } + // if (transaction_id >= 0 && transaction_id <= 255) { + sendAppMessageAck(transaction_id); + // } } catch (JSONException e) { e.printStackTrace(); } break; case PEBBLEKIT_ACTION_APP_ACK: transaction_id = intent.getIntExtra("transaction_id", -1); - if (transaction_id >= 0 && transaction_id <= 255) { - mPebbleIoThread.write(mPebbleProtocol.encodeApplicationMessageAck(null, (byte) transaction_id)); - } else { - LOG.warn("illegal transaction id " + transaction_id); + if (!mPebbleProtocol.mAlwaysACKPebbleKit) { + if (transaction_id >= 0 && transaction_id <= 255) { + mPebbleIoThread.write(mPebbleProtocol.encodeApplicationMessageAck(null, (byte) transaction_id)); + } else { + LOG.warn("illegal transaction id " + transaction_id); + } } break; + case PEBBLEKIT_ACTION_DL_ACK_DATA: + LOG.info("GOT DL DATA ACK"); + break; } } @@ -86,6 +101,7 @@ class PebbleKitSupport { intentFilter.addAction(PEBBLEKIT_ACTION_APP_SEND); intentFilter.addAction(PEBBLEKIT_ACTION_APP_START); intentFilter.addAction(PEBBLEKIT_ACTION_APP_STOP); + intentFilter.addAction(PEBBLEKIT_ACTION_DL_ACK_DATA); mContext.registerReceiver(mPebbleKitReceiver, intentFilter); } @@ -114,4 +130,41 @@ class PebbleKitSupport { } } + void sendDataLoggingIntent(GBDeviceEventDataLogging dataLogging) { + Intent intent = new Intent(); + intent.putExtra("data_log_timestamp", dataLogging.timestamp); + intent.putExtra("uuid", dataLogging.appUUID); + intent.putExtra("data_log_uuid", dataLogging.appUUID); // Is that really the same? + intent.putExtra("data_log_tag", dataLogging.tag); + + switch (dataLogging.command) { + case GBDeviceEventDataLogging.COMMAND_RECEIVE_DATA: + intent.setAction(PEBBLEKIT_ACTION_DL_RECEIVE_DATA_NEW); + intent.putExtra("pbl_data_type", dataLogging.pebbleDataType); + for (Object dataObject : dataLogging.data) { + intent.putExtra("pbl_data_id", dataLogTransactionId++); + switch (dataLogging.pebbleDataType) { + case PebbleProtocol.TYPE_BYTEARRAY: + intent.putExtra("pbl_data_object", Base64.encodeToString((byte[]) dataObject, Base64.NO_WRAP)); + break; + case PebbleProtocol.TYPE_UINT: + intent.putExtra("pbl_data_object", (Long) dataObject); + break; + case PebbleProtocol.TYPE_INT: + intent.putExtra("pbl_data_object", (Integer) dataObject); + break; + } + LOG.info("broadcasting datalogging to uuid " + dataLogging.appUUID + " tag: " + dataLogging.tag + "transaction id: " + dataLogTransactionId + " type: " + dataLogging.pebbleDataType); + mContext.sendBroadcast(intent); + } + break; + case GBDeviceEventDataLogging.COMMAND_FINISH_SESSION: + intent.setAction(PEBBLEKIT_ACTION_DL_FINISH_SESSION); + LOG.info("broadcasting datalogging finish session to uuid " + dataLogging.appUUID + " tag: " + dataLogging.tag); + mContext.sendBroadcast(intent); + break; + default: + LOG.warn("invalid datalog command"); + } + } } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java index 14ae5f9e0..8e6ed14da 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java @@ -28,6 +28,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificati import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes; import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.pebble.GBDeviceEventDataLogging; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleIconID; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp; @@ -224,10 +225,10 @@ public class PebbleProtocol extends GBDeviceProtocol { private static final byte PHONEVERSION_REMOTE_OS_LINUX = 4; private static final byte PHONEVERSION_REMOTE_OS_WINDOWS = 5; - private static final byte TYPE_BYTEARRAY = 0; + static final byte TYPE_BYTEARRAY = 0; private static final byte TYPE_CSTRING = 1; - private static final byte TYPE_UINT = 2; - private static final byte TYPE_INT = 3; + static final byte TYPE_UINT = 2; + static final byte TYPE_INT = 3; private final short LENGTH_PREFIX = 4; @@ -253,6 +254,8 @@ public class PebbleProtocol extends GBDeviceProtocol { private static final Random mRandom = new Random(); int mFwMajor = 3; + boolean mEnablePebbleKit = false; + boolean mAlwaysACKPebbleKit = false; private boolean mForceProtocol = false; private GBDeviceEventScreenshot mDevEventScreenshot = null; private int mScreenshotRemaining = -1; @@ -368,6 +371,7 @@ public class PebbleProtocol extends GBDeviceProtocol { private static final UUID UUID_ZALEWSZCZAK_CROWEX = UUID.fromString("a88b3151-2426-43c6-b1d0-9b288b3ec47e"); private static final UUID UUID_ZALEWSZCZAK_FANCY = UUID.fromString("014e17bf-5878-4781-8be1-8ef998cee1ba"); private static final UUID UUID_ZALEWSZCZAK_TALLY = UUID.fromString("abb51965-52e2-440a-b93c-843eeacb697d"); + private static final UUID UUID_OBSIDIAN = UUID.fromString("ef42caba-0c65-4879-ab23-edd2bde68824"); private static final UUID UUID_ZERO = new UUID(0, 0); @@ -390,6 +394,7 @@ public class PebbleProtocol extends GBDeviceProtocol { mAppMessageHandlers.put(UUID_ZALEWSZCZAK_CROWEX, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_CROWEX, PebbleProtocol.this)); mAppMessageHandlers.put(UUID_ZALEWSZCZAK_FANCY, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_FANCY, PebbleProtocol.this)); mAppMessageHandlers.put(UUID_ZALEWSZCZAK_TALLY, new AppMessageHandlerZalewszczak(UUID_ZALEWSZCZAK_TALLY, PebbleProtocol.this)); + mAppMessageHandlers.put(UUID_OBSIDIAN, new AppMessageHandlerObsidian(UUID_OBSIDIAN, PebbleProtocol.this)); } private final HashMap mDatalogSessions = new HashMap<>(); @@ -455,7 +460,7 @@ public class PebbleProtocol extends GBDeviceProtocol { String title; String subtitle = null; - // for SMS and EMAIL that came in though SMS or K9 receiver + // for SMS that came in though the SMS receiver if (notificationSpec.sender != null) { title = notificationSpec.sender; subtitle = notificationSpec.subject; @@ -571,7 +576,7 @@ public class PebbleProtocol extends GBDeviceProtocol { byte dismiss_action_id; - if (hasHandle) { + if (hasHandle && !"ALARMCLOCKRECEIVER".equals(sourceName)) { actions_count = 3; dismiss_string = "Dismiss"; dismiss_action_id = 0x02; @@ -651,7 +656,7 @@ public class PebbleProtocol extends GBDeviceProtocol { buf.put(dismiss_string.getBytes()); // open and mute actions - if (hasHandle) { + if (hasHandle && !"ALARMCLOCKRECEIVER".equals(sourceName)) { buf.put((byte) 0x01); buf.put((byte) 0x02); // generic buf.put((byte) 0x01); // number attributes @@ -889,7 +894,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } byte dismiss_action_id; - if (hasHandle) { + if (hasHandle && !"ALARMCLOCKRECEIVER".equals(sourceName)) { actions_count = 3; dismiss_string = "Dismiss"; dismiss_action_id = 0x02; @@ -976,7 +981,7 @@ public class PebbleProtocol extends GBDeviceProtocol { buf.put(dismiss_string.getBytes()); // open and mute actions - if (hasHandle) { + if (hasHandle && !"ALARMCLOCKRECEIVER".equals(sourceName)) { buf.put((byte) 0x01); buf.put((byte) 0x02); // generic action buf.put((byte) 0x01); // number attributes @@ -1831,16 +1836,17 @@ public class PebbleProtocol extends GBDeviceProtocol { jsonArray.put(jsonObject); } - // this is a hack we send an ack to the Pebble immediately because we cannot map the transaction_id from the intent back to a uuid yet - /* - GBDeviceEventSendBytes sendBytesAck = new GBDeviceEventSendBytes(); - sendBytesAck.encodedBytes = encodeApplicationMessageAck(uuid, last_id); - */ + GBDeviceEventSendBytes sendBytesAck = null; + if (mAlwaysACKPebbleKit) { + // this is a hack we send an ack to the Pebble immediately because somebody said it helps some PebbleKit apps :P + sendBytesAck = new GBDeviceEventSendBytes(); + sendBytesAck.encodedBytes = encodeApplicationMessageAck(uuid, last_id); + } GBDeviceEventAppMessage appMessage = new GBDeviceEventAppMessage(); appMessage.appUUID = uuid; appMessage.id = last_id & 0xff; appMessage.message = jsonArray.toString(); - return new GBDeviceEvent[]{appMessage}; + return new GBDeviceEvent[]{appMessage, sendBytesAck}; } byte[] encodeApplicationMessagePush(short endpoint, UUID uuid, ArrayList> pairs) { @@ -2208,10 +2214,11 @@ public class PebbleProtocol extends GBDeviceProtocol { return null; } - private GBDeviceEventSendBytes decodeDatalog(ByteBuffer buf, short length) { + private GBDeviceEvent[] decodeDatalog(ByteBuffer buf, short length) { boolean ack = true; byte command = buf.get(); byte id = buf.get(); + GBDeviceEventDataLogging devEvtDataLogging = null; switch (command) { case DATALOG_TIMEOUT: LOG.info("DATALOG TIMEOUT. id=" + (id & 0xff) + " - ignoring"); @@ -2224,7 +2231,14 @@ public class PebbleProtocol extends GBDeviceProtocol { LOG.info("DATALOG SENDDATA. id=" + (id & 0xff) + ", items_left=" + items_left + ", total length=" + (length - 10)); if (datalogSession != null) { LOG.info("DATALOG UUID=" + datalogSession.uuid + ", tag=" + datalogSession.tag + datalogSession.getTaginfo() + ", itemSize=" + datalogSession.itemSize + ", itemType=" + datalogSession.itemType); - ack = datalogSession.handleMessage(buf, length - 10); + if (!datalogSession.uuid.equals(UUID_ZERO) && datalogSession.getClass().equals(DatalogSession.class) && mEnablePebbleKit) { + devEvtDataLogging = datalogSession.handleMessageForPebbleKit(buf, length - 10); + if (devEvtDataLogging == null) { + ack = false; + } + } else { + ack = datalogSession.handleMessage(buf, length - 10); + } } break; case DATALOG_OPENSESSION: @@ -2237,21 +2251,29 @@ 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, getDevice())); + mDatalogSessions.put(id, new DatalogSessionHealthSteps(id, uuid, timestamp, 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, getDevice())); + mDatalogSessions.put(id, new DatalogSessionHealthSleep(id, uuid, timestamp, log_tag, item_type, item_size, getDevice())); } else if (uuid.equals(UUID_ZERO) && log_tag == 84) { - mDatalogSessions.put(id, new DatalogSessionHealthOverlayData(id, uuid, log_tag, item_type, item_size, getDevice())); + mDatalogSessions.put(id, new DatalogSessionHealthOverlayData(id, uuid, timestamp, log_tag, item_type, item_size, getDevice())); } else if (uuid.equals(UUID_ZERO) && log_tag == 85) { - mDatalogSessions.put(id, new DatalogSessionHealthHR(id, uuid, log_tag, item_type, item_size, getDevice())); + mDatalogSessions.put(id, new DatalogSessionHealthHR(id, uuid, timestamp, log_tag, item_type, item_size, getDevice())); } else { - mDatalogSessions.put(id, new DatalogSession(id, uuid, log_tag, item_type, item_size)); + mDatalogSessions.put(id, new DatalogSession(id, uuid, timestamp, log_tag, item_type, item_size)); } } break; case DATALOG_CLOSE: LOG.info("DATALOG_CLOSE. id=" + (id & 0xff)); - if (mDatalogSessions.containsKey(id)) { + datalogSession = mDatalogSessions.get(id); + if (datalogSession != null) { + if (!datalogSession.uuid.equals(UUID_ZERO) && datalogSession.getClass().equals(DatalogSession.class) && mEnablePebbleKit) { + GBDeviceEventDataLogging dataLogging = new GBDeviceEventDataLogging(); + dataLogging.command = GBDeviceEventDataLogging.COMMAND_FINISH_SESSION; + dataLogging.appUUID = datalogSession.uuid; + dataLogging.tag = datalogSession.tag; + devEvtDataLogging = dataLogging; + } mDatalogSessions.remove(id); } break; @@ -2267,7 +2289,8 @@ public class PebbleProtocol extends GBDeviceProtocol { LOG.info("sending NACK (0x86)"); sendBytes.encodedBytes = encodeDatalog(id, DATALOG_NACK); } - return sendBytes; + // append ack/nack + return new GBDeviceEvent[]{devEvtDataLogging, sendBytes}; } private GBDeviceEvent decodeAppReorder(ByteBuffer buf) { @@ -2535,7 +2558,7 @@ public class PebbleProtocol extends GBDeviceProtocol { } break; case ENDPOINT_DATALOG: - devEvts = new GBDeviceEvent[]{decodeDatalog(buf, length)}; + devEvts = decodeDatalog(buf, length); break; case ENDPOINT_SCREENSHOT: devEvts = new GBDeviceEvent[]{decodeScreenshot(buf, length)}; @@ -2582,6 +2605,16 @@ public class PebbleProtocol extends GBDeviceProtocol { mForceProtocol = force; } + void setAlwaysACKPebbleKit(boolean alwaysACKPebbleKit) { + LOG.info("setting always ACK PebbleKit to " + alwaysACKPebbleKit); + mAlwaysACKPebbleKit = alwaysACKPebbleKit; + } + + void setEnablePebbleKit(boolean enablePebbleKit) { + LOG.info("setting enable PebbleKit support to " + enablePebbleKit); + mEnablePebbleKit = enablePebbleKit; + } + private String getFixedString(ByteBuffer buf, int length) { byte[] tmp = new byte[length]; buf.get(tmp, 0, length); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java index 122f6da59..426b65121 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java @@ -12,6 +12,7 @@ import java.util.Iterator; import java.util.UUID; import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.model.Alarm; import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; @@ -113,6 +114,22 @@ public class PebbleSupport extends AbstractSerialDeviceSupport { @Override public void onNotification(NotificationSpec notificationSpec) { + String currentPrivacyMode = GBApplication.getPrefs().getString("pebble_pref_privacy_mode", getContext().getString(R.string.p_pebble_privacy_mode_off)); + if (getContext().getString(R.string.p_pebble_privacy_mode_complete).equals(currentPrivacyMode)) { + notificationSpec.body = null; + notificationSpec.sender = null; + notificationSpec.subject = null; + notificationSpec.title = null; + notificationSpec.phoneNumber = null; + } else if (getContext().getString(R.string.p_pebble_privacy_mode_content).equals(currentPrivacyMode)) { + if (notificationSpec.sender != null) { + notificationSpec.sender = "\n\n\n\n\n" + notificationSpec.sender; + } else if (notificationSpec.title != null) { + notificationSpec.title = "\n\n\n\n\n" + notificationSpec.title; + } else if (notificationSpec.subject != null) { + notificationSpec.subject = "\n\n\n\n\n" + notificationSpec.subject; + } + } if (reconnect()) { super.onNotification(notificationSpec); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java index fd8637bb4..fba114624 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleGATTServer.java @@ -65,11 +65,6 @@ class PebbleGATTServer extends BluetoothGattServerCallback { writeCharacteristics.setValue(new byte[]{(byte) (((serial << 3) | 1) & 0xff)}); mBluetoothGattServer.notifyCharacteristicChanged(mBtDevice, writeCharacteristics, false); - - try { - Thread.sleep(100); // FIXME: bad bad, I mean BAAAD - } catch (InterruptedException ignore) { - } } public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java index e13552d92..07ed20a56 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/ble/PebbleLESupport.java @@ -24,7 +24,7 @@ public class PebbleLESupport { private int mMTU = 20; private int mMTULimit = Integer.MAX_VALUE; boolean mIsConnected = false; - public CountDownLatch mPPAck; + CountDownLatch mPPAck; public PebbleLESupport(Context context, final BluetoothDevice btDevice, PipedInputStream pipedInputStream, PipedOutputStream pipedOutputStream) throws IOException { mBtDevice = btDevice; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java index 9e2ebb41e..82791f62c 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java @@ -166,7 +166,7 @@ public class DeviceHelper { } private List createCoordinators() { - List result = new ArrayList<>(2); + List result = new ArrayList<>(); result.add(new MiBand2Coordinator()); // Note: MiBand2 must come before MiBand because detection is hacky, atm result.add(new MiBandCoordinator()); result.add(new PebbleCoordinator()); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java index 024fb4426..4fc42f0f8 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GBPrefs.java @@ -6,6 +6,8 @@ import java.util.Date; public class GBPrefs { public static final String AUTO_RECONNECT = "general_autocreconnect"; + private static final String AUTO_START = "general_autostartonboot"; + private static final boolean AUTO_START_DEFAULT = true; public static boolean AUTO_RECONNECT_DEFAULT = true; public static final String USER_NAME = "mi_user_alias"; @@ -22,6 +24,10 @@ public class GBPrefs { return mPrefs.getBoolean(AUTO_RECONNECT, AUTO_RECONNECT_DEFAULT); } + public boolean getAutoStart() { + return mPrefs.getBoolean(AUTO_START, AUTO_START_DEFAULT); + } + public String getUserName() { return mPrefs.getString(USER_NAME, USER_NAME_DEFAULT); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/JavaExtensions.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/JavaExtensions.java new file mode 100644 index 000000000..cd4b37cc2 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/JavaExtensions.java @@ -0,0 +1,15 @@ +package nodomain.freeyourgadget.gadgetbridge.util; + +public class JavaExtensions { + + /** + * Equivalent c# '??' operator + * @param one first value + * @param two second value + * @return first if not null, or second if first is null + */ + public static T coalesce(T one, T two) + { + return one != null ? one : two; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/LanguageUtils.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/LanguageUtils.java index 59d7aa817..78ae076c3 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/LanguageUtils.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/LanguageUtils.java @@ -1,5 +1,7 @@ package nodomain.freeyourgadget.gadgetbridge.util; +import org.apache.commons.lang3.text.WordUtils; + import java.util.HashMap; import java.util.Map; import java.text.Normalizer; @@ -23,13 +25,20 @@ public class LanguageUtils { } }; - //check transliterate option status + /** + * Checks the status of transliteration option + * @return true if transliterate option is On, and false, if Off or not exist + */ public static boolean transliterate() { return GBApplication.getPrefs().getBoolean("transliteration", false); } - //replace unsupported symbols to english analog + /** + * Replaces unsupported symbols to english + * @param txt input text + * @return transliterated text + */ public static String transliterate(String txt){ if (txt == null || txt.isEmpty()) { return txt; @@ -47,7 +56,11 @@ public class LanguageUtils { return flattenToAscii(message.toString()); } - //replace unsupported symbol to english analog text + /** + * Replaces unsupported symbol to english by {@code transliterateMap} + * @param c input char + * @return replacement text + */ private static String transliterate(char c){ char lowerChar = Character.toLowerCase(c); @@ -56,7 +69,7 @@ public class LanguageUtils { if (lowerChar != c) { - return replace.toUpperCase(); + return WordUtils.capitalize(replace); } return replace; @@ -65,7 +78,11 @@ public class LanguageUtils { return String.valueOf(c); } - //convert diacritic + /** + * Converts the diacritics + * @param string input text + * @return converted text + */ private static String flattenToAscii(String string) { string = Normalizer.normalize(string, Normalizer.Form.NFD); return string.replaceAll("\\p{M}", ""); diff --git a/app/src/main/res/drawable-hdpi/ic_device_hplus.png b/app/src/main/res/drawable-hdpi/ic_device_hplus.png new file mode 100644 index 000000000..7af95bddc Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_device_hplus.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_device_hplus_disabled.png b/app/src/main/res/drawable-hdpi/ic_device_hplus_disabled.png new file mode 100644 index 000000000..a4a7d7fdb Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_device_hplus_disabled.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_device_hplus.png b/app/src/main/res/drawable-mdpi/ic_device_hplus.png new file mode 100644 index 000000000..88e4c9c13 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_device_hplus.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_device_hplus_disabled.png b/app/src/main/res/drawable-mdpi/ic_device_hplus_disabled.png new file mode 100644 index 000000000..84496f3e7 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_device_hplus_disabled.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_device_hplus.png b/app/src/main/res/drawable-xhdpi/ic_device_hplus.png new file mode 100644 index 000000000..e91f060f8 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_device_hplus.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_device_hplus_disabled.png b/app/src/main/res/drawable-xhdpi/ic_device_hplus_disabled.png new file mode 100644 index 000000000..2c3f219cd Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_device_hplus_disabled.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_device_hplus.png b/app/src/main/res/drawable-xxhdpi/ic_device_hplus.png new file mode 100644 index 000000000..01996225f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_device_hplus.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_device_hplus_disabled.png b/app/src/main/res/drawable-xxhdpi/ic_device_hplus_disabled.png new file mode 100644 index 000000000..a70528df4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_device_hplus_disabled.png differ diff --git a/app/src/main/res/layout/activity_db_management.xml b/app/src/main/res/layout/activity_db_management.xml index feb005a4f..0bf641ccf 100644 --- a/app/src/main/res/layout/activity_db_management.xml +++ b/app/src/main/res/layout/activity_db_management.xml @@ -31,7 +31,6 @@ android:layout_height="wrap_content" android:layout_weight="1" - android:singleLine="false" android:text="Export DB" />