1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-25 19:36:50 +01:00

Merge branch 'master' into background-javascript

This commit is contained in:
Andreas Shimokawa 2017-02-25 13:02:07 +01:00
commit 772a22f78b
94 changed files with 1750 additions and 1395 deletions

View File

@ -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

View File

@ -25,8 +25,9 @@
* Andreas Shimokawa <shimokawa@fsfe.org>
* Carsten Pfeiffer <cpfeiffer@users.noreply.github.com>
* Daniele Gobbetti <daniele+github@gobbetti.name>
* Julien Pivotto <roidelapluie@inuits.eu>
* João Paulo Barraca <jpbarraca@gmail.com>
* ivanovlev <lion.ivanov@gmal.com>
* Julien Pivotto <roidelapluie@inuits.eu>
* Steffen Liebergeld <perl@gmx.org>
* Lem Dulfo <lemuel.dulfo@gmail.com>
* Sergey Trofimov <sarg@sarg.org.ru>
@ -35,9 +36,11 @@
* 0nse <0nse@users.noreply.github.com>
* Gergely Peidl <gergely@peidl.net>
* Christian Fischer <sw-dev@computerlyrik.de>
* Normano64 <per.bergqwist@gmail.com>
* 6arms1leg <m.brnsfld@googlemail.com>
* Normano64 <per.bergqwist@gmail.com>
* Avamander <Avamander@users.noreply.github.com>
* Ⲇⲁⲛⲓ Φi <daniphii@outlook.com>
* Yar <yaroslav.isakov@gmail.com>
* xzovy <caleb@caleb-cooper.net>
* xphnx <xphnx@users.noreply.github.com>
* Tarik Sekmen <tarik@ilixi.org>
@ -51,6 +54,7 @@
* Kevin Richter <me@kevinrichter.nl>
* Kasha <kasha_malaga@hotmail.com>
* Ivan <ivan_tizhanin@mail.ru>
* Hasan Ammar <ammarh@gmail.com>
* Gilles MOREL <contact@gilles-morel.fr>
* Gilles Émilien MOREL <Almtesh@users.noreply.github.com>
* Chris Perelstein <chris.perelstein@gmail.com>

View File

@ -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 {

View File

@ -15,9 +15,9 @@
<uses-permission android:name="android.permission.SEND_SMS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="com.fsck.k9.permission.READ_MESSAGES" />
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-feature
android:name="android.hardware.bluetooth"
@ -226,6 +226,14 @@
</intent-filter>
</activity>
<receiver android:name=".externalevents.AutoStartReceiver"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<receiver
android:name=".externalevents.BluetoothStateChangeReceiver"
android:exported="false">

View File

@ -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;
}

View File

@ -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);

View File

@ -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<Object, ProgressDialog, Object>() {
@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<GBDevice> 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);
}
}

View File

@ -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<? extends Activity> pairingActivity = coordinator.getPairingActivity();
if (pairingActivity != null) {
Intent intent = new Intent(this, pairingActivity);

View File

@ -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<Object, ProgressDialog, Object>() {
@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);
}
}
}

View File

@ -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<BarData> 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<BarEntry> entries = new ArrayList<>();
ArrayList<String> labels = new ArrayList<String>();
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<PieEntry> entries = new ArrayList<>();
List<Integer> 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<Integer> legendColors = new ArrayList<>(1);
// List<String> legendLabels = new ArrayList<>(1);
// legendColors.add(akActivity.color);
// legendLabels.add(getContext().getString(R.string.chart_steps));
// chart.getLegend().setCustom(legendColors, legendLabels);
// chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
}
private List<? extends ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, 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<? extends ActivitySample> 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<BarData> weekBeforeData;
private final DayData dayData;
MyChartsData(DayData dayData, DefaultChartsData<BarData> weekBeforeData) {
this.dayData = dayData;
this.weekBeforeData = weekBeforeData;
}
DayData getDayData() {
return dayData;
}
DefaultChartsData<BarData> getWeekBeforeData() {
return weekBeforeData;
}
}
abstract int getGoal();
abstract int getTotalForSamples(List<? extends ActivitySample> activitySamples);
abstract IValueFormatter getFormatter();
abstract Integer getMainColor();
}

View File

@ -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<? extends ActivitySample> samples) {
class ActivityAnalysis {
ActivityAmounts calculateActivityAmounts(List<? extends ActivitySample> samples) {
ActivityAmount deepSleep = new ActivityAmount(ActivityKind.TYPE_DEEP_SLEEP);
ActivityAmount lightSleep = new ActivityAmount(ActivityKind.TYPE_LIGHT_SLEEP);
ActivityAmount notWorn = new ActivityAmount(ActivityKind.TYPE_NOT_WORN);
@ -64,7 +64,7 @@ public class ActivityAnalysis {
return result;
}
public int calculateTotalSteps(List<? extends ActivitySample> samples) {
int calculateTotalSteps(List<? extends ActivitySample> samples) {
int totalSteps = 0;
for (ActivitySample sample : samples) {
int steps = sample.getSteps();

View File

@ -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);

View File

@ -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<? extends ActivitySample> 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;
}
}

View File

@ -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<BarData> 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<BarEntry> entries = new ArrayList<>();
ArrayList<String> labels = new ArrayList<String>();
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<PieEntry> entries = new ArrayList<>();
List<Integer> 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<Integer> legendColors = new ArrayList<>(1);
// List<String> legendLabels = new ArrayList<>(1);
// legendColors.add(akActivity.color);
// legendLabels.add(getContext().getString(R.string.chart_steps));
// chart.getLegend().setCustom(legendColors, legendLabels);
// chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
}
private List<? extends ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, 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<? extends ActivitySample> activitySamples) {
ActivityAnalysis analysis = new ActivityAnalysis();
return analysis.calculateTotalSteps(activitySamples);
}
@Override
protected List<? extends ActivitySample> 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<BarData> weekBeforeStepsData;
private final DaySteps daySteps;
public MyChartsData(DaySteps daySteps, DefaultChartsData<BarData> weekBeforeStepsData) {
this.daySteps = daySteps;
this.weekBeforeStepsData = weekBeforeStepsData;
}
public DaySteps getDaySteps() {
return daySteps;
}
public DefaultChartsData<BarData> getWeekBeforeStepsData() {
return weekBeforeStepsData;
}
@Override
Integer getMainColor() {
return akActivity.color;
}
}

View File

@ -134,6 +134,14 @@ public class GBDeviceAdapter extends ArrayAdapter<GBDevice> {
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);

View File

@ -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 {

View File

@ -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();
}
}

View File

@ -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";
}

View File

@ -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<? extends AbstractActivitySample> sampleProvider = (AbstractSampleProvider<? extends AbstractActivitySample>) coordinator.getSampleProvider(targetDevice, session);
importActivitySamples(oldDB, targetDevice, session, sampleProvider, user);
break;
}
}
}
}
private <T extends AbstractActivitySample> void importActivitySamples(SQLiteDatabase fromDb, GBDevice targetDevice, DaoSession targetSession, AbstractSampleProvider<T> 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<PebbleHealthActivityOverlay> overlayList = new ArrayList<>();
final int BATCH_SIZE = 100000; // 100.000 samples = rougly 20 MB per batch
List<T> 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();

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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) {
}
}

View File

@ -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 {
}

View File

@ -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;
}

View File

@ -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<Character, Byte> transliterateMap = new HashMap<Character, Byte>(){
{

View File

@ -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);
}
}

View File

@ -30,4 +30,10 @@ public class MakibesF68Coordinator extends HPlusCoordinator {
public DeviceType getDeviceType() {
return DeviceType.MAKIBESF68;
}
@Override
public String getManufacturer() {
return "Makibes";
}
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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";

View File

@ -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

View File

@ -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()) {

View File

@ -204,6 +204,9 @@ public class PBWReader {
}
app = new GBDeviceApp(appUUID, appName, appCreator, appVersion, appType);
}
else if (!isFirmware) {
isValid = false;
}
}
}

View File

@ -45,8 +45,8 @@ public class PebbleHealthSampleProvider extends AbstractSampleProvider<PebbleHea
QueryBuilder<PebbleHealthActivityOverlay> 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<PebbleHealthActivityOverlay> overlayRecords = qb.build().list();
for (PebbleHealthActivityOverlay overlay : overlayRecords) {

View File

@ -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);
}
}

View File

@ -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();
}
}
}

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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.");
}
}
}

View File

@ -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

View File

@ -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() + ")";
}
}

View File

@ -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<? extends Service> 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;
}
}

View File

@ -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";

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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:

View File

@ -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)) {

View File

@ -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<? extends Alarm> 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

View File

@ -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;

View File

@ -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

View File

@ -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;
}
};

View File

@ -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<String> 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<Pair<Integer, Object>> 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);
}
}

View File

@ -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;
}
}

View File

@ -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 + " )";
}

View File

@ -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 + " )";
}

View File

@ -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 + " )";
}

View File

@ -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)";
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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");
}
}
}

View File

@ -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<Byte, DatalogSession> 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<Pair<Integer, Object>> 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);

View File

@ -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);
}

View File

@ -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) {

View File

@ -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;

View File

@ -166,7 +166,7 @@ public class DeviceHelper {
}
private List<DeviceCoordinator> createCoordinators() {
List<DeviceCoordinator> result = new ArrayList<>(2);
List<DeviceCoordinator> 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());

View File

@ -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);
}

View File

@ -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> T coalesce(T one, T two)
{
return one != null ? one : two;
}
}

View File

@ -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}", "");

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -31,7 +31,6 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="false"
android:text="Export DB" />
<Button
@ -65,13 +64,6 @@
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@color/accent" />
<TextView
android:id="@+id/mergeOldActivityDataText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/activity_db_management_merge_old_explanation"
android:textAppearance="?android:attr/textAppearanceSmall" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@ -79,14 +71,6 @@
android:orientation="horizontal"
android:weightSum="2">
<Button
android:id="@+id/mergeOldActivityData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="fill_horizontal"
android:layout_weight="1"
android:text="Import old activity data" />
<Button
android:id="@+id/deleteOldActivityDB"
android:layout_width="wrap_content"

View File

@ -1,31 +0,0 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter">
<Button
android:id="@+id/button_import_old_activitydata"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="fill_horizontal"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:text="@string/import_old_db_buttonlabel" />
<TextView
android:id="@+id/textview_import_old_activitydata"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/button_import_old_activitydata"
android:layout_alignParentEnd="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:text="@string/import_old_db_information" />
</RelativeLayout>

View File

@ -42,6 +42,7 @@
<string name="title_activity_settings">Einstellungen</string>
<string name="pref_header_general">Allgemeine Einstellungen</string>
<string name="pref_title_general_autoconnectonbluetooth">Verbinde, wenn Bluetooth eingeschaltet wird</string>
<string name="pref_title_general_autostartonboot">Automatisch starten</string>
<string name="pref_title_general_autocreonnect">Verbindungen automatisch wiederherstellen</string>
<string name="pref_title_audo_player">Bevorzugter Audioplayer</string>
<string name="pref_default">Standard</string>
@ -56,16 +57,21 @@
<string name="pref_title_notifications_repetitions">Wiederholungen</string>
<string name="pref_title_notifications_call">Anrufe</string>
<string name="pref_title_notifications_sms">SMS</string>
<string name="pref_title_notifications_k9mail">K9-Mail</string>
<string name="pref_title_notifications_pebblemsg">Pebble Nachrichten</string>
<string name="pref_summary_notifications_pebblemsg">Unterstützung für Anwendungen die Benachrichtigungen an die Pebble via PebbleKit senden.</string>
<string name="pref_title_notifications_generic">Andere Benachrichtigungen</string>
<string name="pref_title_whenscreenon">… auch wenn der Bildschirm an ist</string>
<string name="pref_title_notification_filter">Bitte nicht stören</string>
<string name="pref_summary_notification_filter">Stoppe unerwünschte Nachrichten, wenn im \"Nicht Stören\"-Modus</string>
<string name="pref_title_transliteration">Transliteration</string>
<string name="always">immer</string>
<string name="when_screen_off">wenn der Bildschirm aus ist</string>
<string name="never">niemals</string>
<string name="pref_header_privacy">Privatsphäre</string>
<string name="pref_title_call_privacy_mode">Privatsphäre-Modus für Anrufe</string>
<string name="pref_call_privacy_mode_off">Zeige Name und Telefonnumer</string>
<string name="pref_call_privacy_mode_name">Verstecke den Namen aber zeige die Telefonnummer an</string>
<string name="pref_call_privacy_mode_complete">Verstecke Name und Telefonnummer</string>
<string name="pref_blacklist">Sperre für Apps</string>
<string name="pref_header_cannned_messages">Vorgefertigte Nachrichten</string>
<string name="pref_title_canned_replies">Antworten</string>
@ -80,10 +86,15 @@
<string name="pref_title_pebble_sync_health">Pebble Health synchronisieren</string>
<string name="pref_title_pebble_sync_misfit">Misfit synchronisieren</string>
<string name="pref_title_pebble_sync_morpheuz">Morpheuz synchronisieren</string>
<string name="pref_title_enable_outgoing_call">Unterstützung für ausgehende Anrufe</string>
<string name="pref_title_enable_pebblekit">Erlaube Zugriff von anderen Android Apps</string>
<string name="pref_summary_enable_pebblekit">Experimentelle Unterstützung für Android Apps, die PebbleKit benutzen</string>
<string name="pref_title_sunrise_sunset">Sonnenauf- und -untergang </string>
<string name="pref_summary_sunrise_sunset">Sende Sonnenauf- und -untergangszeiten abhänging vom Standort auf die Pebble Timeline</string>
<string name="pref_title_pebble_privacy_mode">Privatsphäre-Modus</string>
<string name="pref_pebble_privacy_mode_off">Normale Benachrichtigungen</string>
<string name="pref_pebble_privacy_mode_content">Verschiebe den Benachrichttigungstext außerhalb des Bildschirms</string>
<string name="pref_pebble_privacy_mode_complete">Zeige nur das Benachrichtigungs-Symbol</string>
<string name="pref_header_location">Standort</string>
<string name="pref_title_location_aquire">Standort Bestimmen</string>
<string name="pref_title_location_latitude">Breitengrad</string>
@ -103,6 +114,8 @@
<string name="pref_title_pebble_enable_applogs">Watch App Logging einschalten</string>
<string name="pref_summary_pebble_enable_applogs">Schreibt logs von Watch Apps in Gadgetbridge logs (Pebble muss nach Ändern der Option erneut verbunden werden)</string>
<string name="pref_title_pebble_reconnect_attempts">Neuverbindungsversuche</string>
<string name="pref_title_unit_system">Einheiten</string>
<string name="pref_title_timeformat">Zeitformat</string>
<string name="not_connected">nicht verbunden</string>
<string name="connecting">verbinde</string>
<string name="connected">verbunden</string>
@ -309,4 +322,5 @@ Wenn Du schon deine Daten importiert hast und mit dem Ergebnis zufrieden bist, k
<!--Strings related to Vibration Activity-->
<string name="title_activity_vibration">Vibration</string>
<!--Strings related to Pebble Pairing Activity-->
<string name="pref_screen_notification_profile_alarm_clock">Wecker</string>
</resources>

View File

@ -45,6 +45,7 @@
<string name="title_activity_settings">Ajustes</string>
<string name="pref_header_general">Ajustes generales</string>
<string name="pref_title_general_autoconnectonbluetooth">Conectarse al dispositivo cuando el Bluetooth esté activado</string>
<string name="pref_title_general_autostartonboot">Iniciar automáticamente</string>
<string name="pref_title_general_autocreonnect">Reconectar automáticamente</string>
<string name="pref_title_audo_player">Reproductor de audio favorito</string>
<string name="pref_default">Predeterminado</string>
@ -62,7 +63,6 @@
<string name="pref_title_notifications_repetitions">Repeticiones</string>
<string name="pref_title_notifications_call">Llamadas telefónicas</string>
<string name="pref_title_notifications_sms">SMS</string>
<string name="pref_title_notifications_k9mail">K9-Mail</string>
<string name="pref_title_notifications_pebblemsg">Mensajes de Pebble</string>
<string name="pref_summary_notifications_pebblemsg">Soporte para aplicaciones que envían notificaciones a Pebble a través de PebbleKit.</string>
<string name="pref_title_notifications_generic">Soporte para notificaciones genéricas</string>
@ -74,6 +74,11 @@
<string name="always">siempre</string>
<string name="when_screen_off">cuando la pantalla está apagada</string>
<string name="never">nunca</string>
<string name="pref_header_privacy">Privacidad</string>
<string name="pref_title_call_privacy_mode">Modo de privacidad de llamada</string>
<string name="pref_call_privacy_mode_off">Mostrar nombre y número</string>
<string name="pref_call_privacy_mode_name">Ocultar nombre pero mostrar número</string>
<string name="pref_call_privacy_mode_complete">Ocultar nombre y número</string>
<string name="pref_blacklist">Excluir aplicaciones</string>
<string name="pref_header_cannned_messages">Mensajes predeterminados</string>
<string name="pref_title_canned_replies">Respuestas</string>
@ -96,6 +101,10 @@
<string name="pref_summary_sunrise_sunset">Enviar las horas de salida y puesta de Sol basándose en la localización a la línea cronológica del Pebble</string>
<string name="pref_title_autoremove_notifications">Eliminar automáticamente las notificaciones rechazadas</string>
<string name="pref_summary_autoremove_notifications">Las notificaciones se borran automáticamente de Pebble cuando son rechazadas en Android</string>
<string name="pref_title_pebble_privacy_mode">Modo privado</string>
<string name="pref_pebble_privacy_mode_off">Notificación normal</string>
<string name="pref_pebble_privacy_mode_content">Desplaza la notificación fuera de la pantalla</string>
<string name="pref_pebble_privacy_mode_complete">Muestra solo el icono de notificación</string>
<string name="pref_header_location">Localización</string>
<string name="pref_title_location_aquire">Buscar localización</string>
<string name="pref_title_location_latitude">Latitud</string>
@ -114,7 +123,14 @@
<string name="pref_summary_pebble_mtu_limit">Si tu Pebble 2/Pebble LE no funciona correctamente, prueba esta opción para limitar el MTU (rango válido 20512)</string>
<string name="pref_title_pebble_enable_applogs">Activar crear registros de la App del Reloj</string>
<string name="pref_summary_pebble_enable_applogs">Producirá registros de las apps del reloj que Gadgetbridge guardará (necesita reconexión)</string>
<string name="pref_title_pebble_always_ack_pebblekit">ACK antes de tiempo de PebbleKit</string>
<string name="pref_summary_pebble_always_ack_pebblekit">Permitirá a los mensajes enviados a apps de terceros ser reconocidos siempre e inmediatamente</string>
<string name="pref_title_pebble_reconnect_attempts">Intentos de reconexión</string>
<string name="pref_title_unit_system">Unidades</string>
<string name="pref_title_timeformat">Formato de hora</string>
<string name="pref_title_screentime">Tiempo de pantalla encendida</string>
<string name="prefs_title_all_day_heart_rate">Medición del ritmo cardíaco todo el día</string>
<string name="preferences_hplus_settings">Ajustes de HPlus/Makibes</string>
<string name="not_connected">no conectado</string>
<string name="connecting">conectando</string>
<string name="connected">conectado</string>
@ -338,4 +354,11 @@ Por favor, ten en cuenta que puedes importar datos desde Mi Band, Pebble Health
<string name="title_activity_pebble_pairing">Emparejando con Pebble</string>
<string name="pebble_pairing_hint">En su dispositivo Android va a aparecer un mensaje para emparejarse. Si no apareciera, mira en el cajón de notificaciones y acepta la propuesta de emparejamiento. Después acepta también en tu Pebble.</string>
<string name="weather_notification_label">Asegúrate de que este tema esté activado en la aplicación de notificación del tiempo para obtener la información en tu Pebble.\n\nNo se requiere configuración.\n\nPuedes activar la aplicación del tiempo del sistema desde la configuración de la app.\n\nLas watchfaces soportadas mostrarán la información del tiempo automáticamente.</string>
<string name="pref_title_setup_bt_pairing">Activar el emparejamiento Bluetooth</string>
<string name="pref_summary_setup_bt_pairing">Desactiva esto si tienes problemas de conexión</string>
<string name="unit_metric">Métrica</string>
<string name="unit_imperial">Imperial</string>
<string name="timeformat_24h">24H</string>
<string name="timeformat_am_pm">AM/PM</string>
<string name="pref_screen_notification_profile_alarm_clock">Despertador</string>
</resources>

View File

@ -27,6 +27,9 @@
<string name="appmanager_health_deactivate">Désactiver</string>
<string name="appmanager_hrm_activate">Activer la mesure du rythme cardiaque</string>
<string name="appmanager_hrm_deactivate">Désactiver la mesure du rythme cardiaque</string>
<string name="appmanager_weather_activate">Activer l\'application météo du système</string>
<string name="appmanager_weather_deactivate">Désactiver l\'application météo du système</string>
<string name="appmanager_weather_install_provider">Installer l\'application de notification de la météo</string>
<string name="app_configure">Configurer</string>
<string name="app_move_to_top">Haut de page </string>
<!--Strings related to AppBlacklist-->
@ -37,11 +40,12 @@
<string name="fw_multi_upgrade_notice">Vous êtes sur le point d\'installer les micrologiciels %1$s et %2$s à la place de ceux qui sont actuellement sur votre Mi Band.</string>
<string name="miband_firmware_known">Ce micrologiciel a été testé et est connu pour être compatible avec Gadgetbridge.</string>
<string name="miband_firmware_unknown_warning">Ce micrologiciel n\'a pas été testé et peut ne pas être compatible avec Gadgetbridge.\n\nIl n\'est pas conseillé de le flasher sur votre Mi Band.</string>
<string name="miband_firmware_suggest_whitelist">Si vous désirez continuer et que tout fonctionne correctement par la suite, SVP informez-en les développeurs de Gadgetbridge pour demander l\'ajout de ce micrologiciel à leur liste: %s</string>
<string name="miband_firmware_suggest_whitelist">Si vous désirez continuer et que tout fonctionne correctement par la suite, veuillez en informer les développeurs de Gadgetbridge pour demander l\'ajout de ce micrologiciel à leur liste: %s</string>
<!--Strings related to Settings-->
<string name="title_activity_settings">Paramètres</string>
<string name="pref_header_general">Paramètres généraux</string>
<string name="pref_title_general_autoconnectonbluetooth">Connecter votre appareil quand le Bluetooth est mis en marche</string>
<string name="pref_title_general_autostartonboot">Démarrer automatiquement</string>
<string name="pref_title_general_autocreonnect">Reconnexion automatique </string>
<string name="pref_title_audo_player">Lecteur audio préféré</string>
<string name="pref_default">Par défaut</string>
@ -52,20 +56,29 @@
<string name="pref_theme_light">Clair</string>
<string name="pref_theme_dark">Sombre</string>
<string name="pref_title_language">Langue</string>
<string name="pref_title_minimize_priority">Masquer la notification de Gadgetbridge</string>
<string name="pref_summary_minimize_priority_off">L\'icône de la barre d\'état et la notification de l\'écran de verrouillage sont affichées</string>
<string name="pref_summary_minimize_priority_on">L\'icône de la barre d\'état et la notification de l\'écran de verrouillage sont masquées</string>
<string name="pref_header_notifications">Notifications</string>
<string name="pref_title_notifications_repetitions">Répétitions</string>
<string name="pref_title_notifications_call">Appels téléphoniques</string>
<string name="pref_title_notifications_sms">Textos</string>
<string name="pref_title_notifications_k9mail">K9-Mail</string>
<string name="pref_title_notifications_pebblemsg">Messages Pebble</string>
<string name="pref_summary_notifications_pebblemsg">Support des applications qui envoient des notification à Pebble par PebbleKit.</string>
<string name="pref_title_notifications_generic">Support des notifications génériques</string>
<string name="pref_title_whenscreenon">... y compris quand l\'écran est allumé</string>
<string name="pref_title_notification_filter">Ne Pas Déranger</string>
<string name="pref_summary_notification_filter">Arrêter lenvoi des notifications non-désirées en mode Ne Pas Déranger.</string>
<string name="pref_title_transliteration">Transcription</string>
<string name="pref_summary_transliteration">À utiliser si l\'appareil ne supporte pas la police de caractères (pour l\'instant seulement le Cyrillique)</string>
<string name="always">toujours</string>
<string name="when_screen_off">quand l\'écran est éteint</string>
<string name="never">jamais</string>
<string name="pref_header_privacy">Confidentialité</string>
<string name="pref_title_call_privacy_mode">Mode de confidentialité d\'appel</string>
<string name="pref_call_privacy_mode_off">Afficher le nom et le numéro</string>
<string name="pref_call_privacy_mode_name">Masquer le nom mais afficher le numéro</string>
<string name="pref_call_privacy_mode_complete">Masquer le nom et le numéro</string>
<string name="pref_blacklist">Applications bloquées</string>
<string name="pref_header_cannned_messages">Modèles de messages</string>
<string name="pref_title_canned_replies">Réponses</string>
@ -80,10 +93,18 @@
<string name="pref_title_pebble_sync_health">Synchroniser Pebble Health</string>
<string name="pref_title_pebble_sync_misfit">Synchroniser Misfit</string>
<string name="pref_title_pebble_sync_morpheuz">Synchroniser Morpheuz</string>
<string name="pref_title_enable_outgoing_call">Support des appels sortants</string>
<string name="pref_summary_enable_outgoing_call">Désactiver ceci empêchera la Pebble 2/LE de vibrer lors des appels sortants</string>
<string name="pref_title_enable_pebblekit">Permettre l\'accès aux applications tierces Android</string>
<string name="pref_summary_enable_pebblekit">Activer le support expérimental pour les applications Android utilisant PebbleKit</string>
<string name="pref_title_sunrise_sunset">Lever et coucher de soleil</string>
<string name="pref_summary_sunrise_sunset">Envoyer les heures de lever et coucher du soleil dans lhistorique Pebble en fonction de l\'emplacement</string>
<string name="pref_title_autoremove_notifications">Effacer automatiquement les notifications rejetées</string>
<string name="pref_summary_autoremove_notifications">Les notifications sont automatiquement effacées de la Pebble lorsqu\'elles sont rejetées sur l\'appareil Android</string>
<string name="pref_title_pebble_privacy_mode">Mode privé</string>
<string name="pref_pebble_privacy_mode_off">Notifications normales</string>
<string name="pref_pebble_privacy_mode_content">Déplace les notifications hors de l\'écran</string>
<string name="pref_pebble_privacy_mode_complete">Ne montre que l\'icône de notification</string>
<string name="pref_header_location">Emplacement</string>
<string name="pref_title_location_aquire">Obtenir l\'emplacement</string>
<string name="pref_title_location_latitude">Latitude</string>
@ -97,12 +118,19 @@
<string name="pref_title_pebble_forceuntested">Activer les fonctionnalités non-testées</string>
<string name="pref_summary_pebble_forceuntested">Activer les fonctionnalités non-testées. ACTIVEZ UNIQUEMENT SI VOUS SAVEZ CE QUE VOUS FAITES!</string>
<string name="pref_title_pebble_forcele">Toujours préférer le BLE</string>
<string name="pref_summary_pebble_forcele">Utiliser le support expérimental du LE pour toutes les Pebble au lieu du Bluetooth classique ; cela requiert lappairage d\'une \"Pebble LE\" après quune non-LE ai déjà été connectée</string>
<string name="pref_summary_pebble_forcele">Utiliser le support expérimental du LE pour toutes les Pebble au lieu du Bluetooth classique ; cela requiert lappairage d\'une \"Pebble LE\" après quune non-LE ait déjà été connectée</string>
<string name="pref_title_pebble_mtu_limit">Limite du GATT MTU de Pebble 2/LE</string>
<string name="pref_summary_pebble_mtu_limit">Si votre Pebble 2/LE ne fonctionne pas correctement, essayez d\'activer cette option pour limiter le MTU (plage valide 20-512)</string>
<string name="pref_title_pebble_enable_applogs">Activer les logs des Watch App</string>
<string name="pref_summary_pebble_enable_applogs">Ceci permettra à Gadgetbridge de conserver les logs des Watch App (requiert une reconnexion)</string>
<string name="pref_title_pebble_always_ack_pebblekit">ACK à l\'avance du PebbleKit</string>
<string name="pref_summary_pebble_always_ack_pebblekit">Ceci permettra aux messages envoyés à des apps tierces d\'être toujours reconnus immédiatement</string>
<string name="pref_title_pebble_reconnect_attempts">Tentatives de reconnexion</string>
<string name="pref_title_unit_system">Unités</string>
<string name="pref_title_timeformat">Format de l\'heure</string>
<string name="pref_title_screentime">Durée d\'écran allumé</string>
<string name="prefs_title_all_day_heart_rate">Mesure de la fréquence cardiaque toute la journée</string>
<string name="preferences_hplus_settings">Paramètres HPlus/Makibes</string>
<string name="not_connected">non connecté</string>
<string name="connecting">connexion en cours</string>
<string name="connected">connecté</string>
@ -118,13 +146,13 @@
<string name="tap_connected_device_for_app_mananger">Cliquez sur l\'appareil pour ouvrir le gestionnaire d\'application</string>
<string name="tap_connected_device_for_activity">Cliquez sur l\'appareil pour ouvrir le gestionnaire dactivité</string>
<string name="tap_connected_device_for_vibration">Cliquez sur connecter pour envoyer une vibration</string>
<string name="tap_a_device_to_connect">Tapotter sur le périphérique pour le connecter.</string>
<string name="tap_a_device_to_connect">Tapottez sur le périphérique pour le connecter.</string>
<string name="cannot_connect_bt_address_invalid_">Connexion impossible. Ladresse Bluetooth est-elle valide? </string>
<string name="gadgetbridge_running">Gadgetbridge est en fonctionnement</string>
<string name="installing_binary_d_d">Installation du fichier %1$d/%2$d</string>
<string name="installation_failed_">échec de l\'installation !</string>
<string name="installation_successful">Installation réalisée avec succès</string>
<string name="firmware_install_warning">VOUS TENTEZ D\'INSTALLER UN MICROLOGICIEL, PROCÉDEZ À VOS PROPRES RISQUES.\n\n\nCe micrologiciel est pour la version de matériel: %s</string>
<string name="firmware_install_warning">VOUS TENTEZ D\'INSTALLER UN MICROLOGICIEL, PROCÉDEZ À VOS RISQUES ET PÉRILS.\n\n\nCe micrologiciel est pour la version de matériel: %s</string>
<string name="app_install_info">Vous êtes sur le point d\'installer l\'application suivante:\n\n\n%1$s Version %2$s par %3$s\n</string>
<string name="n_a">N.D.</string>
<string name="initialized">Initialisé</string>
@ -236,13 +264,13 @@
<string name="pref_title_dont_ack_transfer">Ne pas confirmer le transfert de données d\'activités</string>
<string name="pref_summary_dont_ack_transfers">Les données d\'activités ne seront pas effacées du bracelet si elles ne sont pas confirmées. Utile si GB est utilisé avec d\'autres applications.</string>
<string name="pref_summary_keep_data_on_device">Les données d\'activités seront conservées sur le Mi Band après la synchronisation. Utile si GB est utilisé avec d\'autres applications.</string>
<string name="pref_title_low_latency_fw_update">Utilisez le mode low-latency pour les mises à jour FW</string>
<string name="pref_summary_low_latency_fw_update">Cela peu aider sur les appareils où les mises à jour du firmware échouent</string>
<string name="pref_title_low_latency_fw_update">Utilisez le mode basse latence pour les mises à jour du micrologiciel</string>
<string name="pref_summary_low_latency_fw_update">Cela peut aider sur les appareils où les mises à jour du micrologiciel échouent</string>
<string name="live_activity_steps_history">Historique de pas</string>
<string name="live_activity_current_steps_per_minute">Pas/minute actuel</string>
<string name="live_activity_total_steps">Nombre total de pas</string>
<string name="live_activity_steps_per_minute_history">Historique de pas/minute</string>
<string name="live_activity_start_your_activity">Démarrer votre activité</string>
<string name="live_activity_start_your_activity">Démarrez votre activité</string>
<string name="abstract_chart_fragment_kind_activity">Activité</string>
<string name="abstract_chart_fragment_kind_light_sleep">Sommeil léger</string>
<string name="abstract_chart_fragment_kind_deep_sleep">Sommeil profond</string>
@ -253,15 +281,15 @@
<string name="miband_fwinstaller_incompatible_version">Micrologiciel non compatible</string>
<string name="fwinstaller_firmware_not_compatible_to_device">Ce micrologiciel n\'est pas compatible avec l\'appareil</string>
<string name="miband_prefs_reserve_alarm_calendar">Alarmes à réserver pour événements futurs</string>
<string name="miband_prefs_hr_sleep_detection">Utiliser le capteur cardiaque pour améliorer la précision du sommeil</string>
<string name="miband_prefs_device_time_offset_hours">La compensation de temps en heure (pour détecter le sommeil de travailleurs en rotation, par exemple)</string>
<string name="miband_prefs_hr_sleep_detection">Utiliser le capteur cardiaque pour améliorer la détection du sommeil</string>
<string name="miband_prefs_device_time_offset_hours">La compensation de temps en heure (pour travailleurs en rotation, par exemple)</string>
<string name="miband2_prefs_dateformat">Mi2 : Format de la date</string>
<string name="dateformat_time">Heure</string>
<string name="dateformat_date_time"><![CDATA[Time & Date]]></string>
<string name="mi2_prefs_activate_display_on_lift">Allumer l\'écran lors d\'un mouvement</string>
<string name="FetchActivityOperation_about_to_transfer_since">Sur le point de transférer des données depuis %1$s</string>
<string name="waiting_for_reconnect">en attente de reconnexion</string>
<string name="activity_prefs_about_you">A propos de vous</string>
<string name="activity_prefs_about_you">À propos de vous</string>
<string name="activity_prefs_year_birth">Année de naissance</string>
<string name="activity_prefs_gender">Genre</string>
<string name="activity_prefs_height_cm">Taille en cm</string>
@ -276,50 +304,50 @@
<string name="device_fw">Micrologiciel: %1$s</string>
<string name="error_creating_directory_for_logfiles">Erreur à la création de votre fichier log : %1$s</string>
<string name="DEVINFO_HR_VER">HR:</string>
<string name="updatefirmwareoperation_update_in_progress">Le firmware se met à jour</string>
<string name="updatefirmwareoperation_update_in_progress">Le micrologiciel se met à jour</string>
<string name="updatefirmwareoperation_firmware_not_sent">Échec lors de l\'écriture du micrologiciel</string>
<string name="charts_legend_heartrate">Rythme cardiaque</string>
<string name="live_activity_heart_rate">Rythme cardiaque</string>
<!--Strings related to Onboading Activity-->
<string name="title_activity_onboarding">Importer la base de donnée</string>
<string name="import_old_db_buttonlabel">Importer des données d\'activité ancienne</string>
<string name="import_old_db_buttonlabel">Importer des données d\'activité anciennes</string>
<string name="import_old_db_information">Depuis Gadgetbridge 0.12.0, nous utilisons un nouveau format de base de données.
Vous êtes en mesure d\'importer des données d\'activité ancienne et l\'associer à l\'appareil que vous vous connectez à (%1$s).\n
Vous êtes en mesure d\'importer des données d\'activité anciennes et de les associer à l\'appareil auquel vous vous connectez (%1$s).\n
\n
Si vous ne voulez pas importer les anciennes données maintenant, vous pouvez toujours le faire plus tard en appuyant sur le bouton FUSIONNER LES ANCIENNES DONNÉES DACTIVITÉ » dans le Gestionnaire de base de données\n
Si vous ne voulez pas importer les anciennes données maintenant, vous pouvez toujours le faire plus tard en appuyant sur le bouton \"FUSIONNER LES ANCIENNES DONNÉES DACTIVITÉ\" dans le gestionnaire de base de données\n
\n
Notez que vous pouvez importer des données de Mi Band, Pebble Health et Morpheuz mais PAS de Pebble Misfit.</string>
<string name="pref_title_pebble_health_store_raw">Stockez les enregistrement brut dans la base de données</string>
<string name="pref_summary_pebble_health_store_raw">Si coché, les données sont stockées \"tel quel\" et seront disponible pour interprétation ultérieurement (lorsque-nous serons en mesure de mieux les comprendre).
NOTE : la base de données sera bien évidement plus grande !</string>
<string name="pref_title_pebble_health_store_raw">Stockez les enregistrements brut dans la base de données</string>
<string name="pref_summary_pebble_health_store_raw">Si coché, les données sont stockées \"telles quelles\" et seront disponibles pour une interprétation ultérieure.
NOTE: la base de données sera bien évidement plus grande !</string>
<string name="action_db_management">Gestion de base de données</string>
<string name="title_activity_db_management">Gestion de base de données</string>
<string name="activity_db_management_import_export_explanation">Les opérations sur la base de donnée ont utilisé le chemin suivant sur votre appareil.\n Ce chemin n\'est pas accessible par d\'autres applications Android ou par votre ordinateur./nBase de donnée à attendu (Vous pouvez mettre aussi la base de donnée que vous souhaitez importer ici) :</string>
<string name="activity_db_management_merge_old_explanation">Les données d\'activité enregistrées avec les versions antérieures à la 0.12 de Gadgetbridge doivent être convertis en un nouveau format.\n
<string name="activity_db_management_import_export_explanation">Les opérations sur la base de donnée ont utilisé le chemin suivant sur votre appareil.\n Ce chemin n\'est pas accessible par d\'autres applications Android ou par votre ordinateur./nVous trouverez votre base de données (ou celle que vous souhaitez importer) ici:</string>
<string name="activity_db_management_merge_old_explanation">Les données d\'activité enregistrées avec les versions antérieures à la 0.12 de Gadgetbridge doivent être converties en un nouveau format.\n
Vous pouvez le faire en utilisant le bouton ci-dessous. Soyez conscient que vous devez être connecté à l\'appareil que vous souhaitez associer avec les anciennes données d\'activité !\n
Si vous avez déjà importé vos données et êtes satisfait du résultat, vous pouvez supprimer l\'ancienne base de données.</string>
<string name="activity_db_management_merge_old_title">Import / Suppression des anciennes Base de Données.</string>
<string name="activity_db_management_merge_old_title">Import / Suppression des anciennes bases de données.</string>
<string name="dbmanagementactivvity_cannot_access_export_path">Impossible d\'accéder au fichier d\'export. Merci de contacter les développeurs.</string>
<string name="dbmanagementactivity_exported_to">Exporter vers : %1$s</string>
<string name="dbmanagementactivity_error_exporting_db">Erreur d\'exportation BD: %1$s</string>
<string name="dbmanagementactivity_import_data_title">Importer des données ?</string>
<string name="dbmanagementactivity_overwrite_database_confirmation">Voulez vous vraiment effacer la base de donnée actuel ? Toutes vos données (si vous en avez) seront perdu.</string>
<string name="dbmanagementactivity_import_successful">Importation réussit.</string>
<string name="dbmanagementactivity_overwrite_database_confirmation">Voulez-vous vraiment effacer la base de données actuelle ? Toutes vos données (si vous en avez) seront perdues.</string>
<string name="dbmanagementactivity_import_successful">Importation réussie.</string>
<string name="dbmanagementactivity_error_importing_db">Erreur lors de l\'importation BD: %1$s</string>
<string name="dbmanagementactivity_no_old_activitydatabase_found">Aucune ancienne base de donnée trouvé, rien à importer.</string>
<string name="dbmanagementactivity_no_connected_device">Pas d\'appareil connecté à associer avec l\'ancienne base de donnée.</string>
<string name="dbmanagementactivity_merging_activity_data_title">Fusion des données d\'activités</string>
<string name="dbmanagementactivity_no_old_activitydatabase_found">Aucune ancienne base de données trouvée, rien à importer.</string>
<string name="dbmanagementactivity_no_connected_device">Pas d\'appareil connecté à associer avec l\'ancienne base de données.</string>
<string name="dbmanagementactivity_merging_activity_data_title">Fusion des données d\'activité</string>
<string name="dbmanagementactivity_please_wait_while_merging">Merci d\'attendre pendant la fusion de vos données.</string>
<string name="dbmanagementactivity_error_importing_old_activity_data">Échec de l\'import des anciennes données d\'activité dans la nouvelle base de donnée.</string>
<string name="dbmanagementactivity_associate_old_data_with_device">Association des ancienne donnée avec le nouvel appareil.</string>
<string name="dbmanagementactivity_delete_activity_data_title">Détruire les ancienne données ?</string>
<string name="dbmanagementactivity_really_delete_entire_db">Voulez vous vraiment détruire entièrement la base de donnée ? Toutes vos données d\'activités et vos informations issus de vos appareils seront perdu.</string>
<string name="dbmanagementactivity_error_importing_old_activity_data">Échec de l\'import des anciennes données d\'activité dans la nouvelle base de données.</string>
<string name="dbmanagementactivity_associate_old_data_with_device">Association des anciennes données avec le nouvel appareil.</string>
<string name="dbmanagementactivity_delete_activity_data_title">Détruire les anciennes données ?</string>
<string name="dbmanagementactivity_really_delete_entire_db">Voulez-vous vraiment détruire entièrement la base de données ? Toutes vos données d\'activité et vos informations issues de vos appareils seront perdues.</string>
<string name="dbmanagementactivity_database_successfully_deleted">Les données ont été effacées.</string>
<string name="dbmanagementactivity_db_deletion_failed">Échec de la destruction de la base de donnée.</string>
<string name="dbmanagementactivity_delete_old_activity_db">Voulez vous détruire les anciennes activités de la base ?</string>
<string name="dbmanagementactivity_delete_old_activitydb_confirmation">Voulez vous vraiment détruire entièrement la base de donnée ? Toutes vos données non importé seront perdu.</string>
<string name="dbmanagementactivity_old_activity_db_successfully_deleted">Les anciennes données d\'activité ont été effacées correctement.</string>
<string name="dbmanagementactivity_old_activity_db_deletion_failed">Échec de la destruction de l\'ancienne base de donnée.</string>
<string name="dbmanagementactivity_db_deletion_failed">Échec de la destruction de la base de données.</string>
<string name="dbmanagementactivity_delete_old_activity_db">Voulez-vous détruire les anciennes activités de la base de données ?</string>
<string name="dbmanagementactivity_delete_old_activitydb_confirmation">Voulez-vous vraiment détruire entièrement la base de données ? Toutes vos données non importées seront perdues.</string>
<string name="dbmanagementactivity_old_activity_db_successfully_deleted">Les anciennes données d\'activité ont été correctement effacées.</string>
<string name="dbmanagementactivity_old_activity_db_deletion_failed">Échec de la destruction de l\'ancienne base de données.</string>
<string name="dbmanagementactivity_overwrite">Écraser</string>
<string name="Cancel">Annuler</string>
<string name="Delete">Supprimer</string>
@ -328,4 +356,12 @@ Si vous avez déjà importé vos données et êtes satisfait du résultat, vous
<!--Strings related to Pebble Pairing Activity-->
<string name="title_activity_pebble_pairing">Appairage avec une Pebble</string>
<string name="pebble_pairing_hint">Une fenêtre dappairage va safficher sur votre téléphone. Si cela ne se produit pas, regardez dans vos notifications et acceptez la demande dappairage. Acceptez ensuite la demande dappairage sur votre Pebble.</string>
<string name="weather_notification_label">Assurez vous que ce thème soit activé dans l\'application de notification de la météo pour recevoir les informations sur votre Pebble.\n\nAucune configuration n\'est requise.\n\nVous pouvez activer l\'application météo système de votre Pebble depuis la configuration de l\'application.\n\nLes watchfaces supportées afficheront la météo automatiquement.</string>
<string name="pref_title_setup_bt_pairing">Activer l\'appairage Bluetooth</string>
<string name="pref_summary_setup_bt_pairing">Désactivez ceci si vous avez des problèmes de connexion</string>
<string name="unit_metric">Mesure</string>
<string name="unit_imperial">Impériale</string>
<string name="timeformat_24h">24H</string>
<string name="timeformat_am_pm">AM/PM</string>
<string name="pref_screen_notification_profile_alarm_clock">Réveil</string>
</resources>

View File

@ -56,7 +56,6 @@
<string name="pref_title_notifications_repetitions">Ismétlések</string>
<string name="pref_title_notifications_call">Telefonhívások</string>
<string name="pref_title_notifications_sms">SMS</string>
<string name="pref_title_notifications_k9mail">K9-Mail</string>
<string name="pref_title_notifications_pebblemsg">Pebble üzenetek</string>
<string name="pref_summary_notifications_pebblemsg">Támogatás az alkalmazásoknak, amik értesítéseket küldenek a Pebble-nek a PebbleKit-en keresztül.</string>
<string name="pref_title_notifications_generic">Általános értesítési támogatás</string>

View File

@ -54,14 +54,12 @@
<string name="pref_theme_light">Chiaro</string>
<string name="pref_theme_dark">Scuro</string>
<string name="pref_title_language">Lingua</string>
<string name="pref_title_minimize_priority">Nascondi la notifica di gadgetbridge</string>
<string name="pref_summary_minimize_priority_off">L\'icona nella barra di stato e la notifica nella schermata di blocco vengono mostrate</string>
<string name="pref_summary_minimize_priority_on">L\'icona nella barra di stato e la notifica nella schermata di blocco non vengono mostrate</string>
<string name="pref_header_notifications">Notifiche</string>
<string name="pref_title_notifications_repetitions">Ripetizioni</string>
<string name="pref_title_notifications_call">Chiamate telefoniche</string>
<string name="pref_title_notifications_sms">SMS</string>
<string name="pref_title_notifications_k9mail">K9-Mail</string>
<string name="pref_title_notifications_pebblemsg">Messaggi Pebble</string>
<string name="pref_summary_notifications_pebblemsg">Supporto per applicazioni che inviano le notifiche a Pebble usando PebbleKit.</string>
<string name="pref_title_notifications_generic">Notifiche generiche</string>

View File

@ -45,6 +45,7 @@
<string name="title_activity_settings">設定</string>
<string name="pref_header_general">一般設定</string>
<string name="pref_title_general_autoconnectonbluetooth">Bluetoothがオンになったときにデバイスに接続</string>
<string name="pref_title_general_autostartonboot">自動的に開始</string>
<string name="pref_title_general_autocreonnect">自動的に再接続</string>
<string name="pref_title_audo_player">お好みのオーディオプレイヤー</string>
<string name="pref_default">デフォルト</string>
@ -62,7 +63,6 @@
<string name="pref_title_notifications_repetitions">繰り返し</string>
<string name="pref_title_notifications_call">電話通知</string>
<string name="pref_title_notifications_sms">SMS</string>
<string name="pref_title_notifications_k9mail">K9メール</string>
<string name="pref_title_notifications_pebblemsg">Pebbleメッセージ</string>
<string name="pref_summary_notifications_pebblemsg">PebbleKit経由でPebbleに通知を送信するアプリケーションをサポートします。</string>
<string name="pref_title_notifications_generic">一般ステータス通知対応</string>
@ -70,10 +70,15 @@
<string name="pref_title_notification_filter">サイレント</string>
<string name="pref_summary_notification_filter">サイレントモードに基づいて、送信される不要な通知を停止します。</string>
<string name="pref_title_transliteration">音訳</string>
<string name="pref_summary_transliteration">デバイスでお使いの言語のフォントがサポートされていない場合に、使用してください (現在はキリル文字のみ)</string>
<string name="pref_summary_transliteration">デバイスでお使いの言語のフォントがサポートされていない場合に、これを有効にしてください (現在はキリル文字のみ)</string>
<string name="always">いつも</string>
<string name="when_screen_off">スクリーンがオフのとき</string>
<string name="never">なし</string>
<string name="pref_header_privacy">プライバシー</string>
<string name="pref_title_call_privacy_mode">通話プライバシーモード</string>
<string name="pref_call_privacy_mode_off">名前と番号を表示</string>
<string name="pref_call_privacy_mode_name">名前は非表示、番号は表示</string>
<string name="pref_call_privacy_mode_complete">名前と番号を非表示</string>
<string name="pref_blacklist">アップのブラックリスト</string>
<string name="pref_header_cannned_messages">定型のメッセージ</string>
<string name="pref_title_canned_replies">返信</string>
@ -96,6 +101,10 @@
<string name="pref_summary_sunrise_sunset">場所に基づいて、Pebble のタイムラインに日の出・日の入りの時間を送ります</string>
<string name="pref_title_autoremove_notifications">消去した通知を自動削除</string>
<string name="pref_summary_autoremove_notifications">通知は、Androidデバイスから削除されたときに、Pebbleから自動的に削除されます</string>
<string name="pref_title_pebble_privacy_mode">プライバシーモード</string>
<string name="pref_pebble_privacy_mode_off">通常の通知</string>
<string name="pref_pebble_privacy_mode_content">通知テキストを画面外にシフト</string>
<string name="pref_pebble_privacy_mode_complete">通知アイコンのみを表示</string>
<string name="pref_header_location">場所</string>
<string name="pref_title_location_aquire">場所の取得</string>
<string name="pref_title_location_latitude">緯度</string>
@ -114,7 +123,14 @@
<string name="pref_summary_pebble_mtu_limit">Pebble 2/Pebble LE が期待どおりに機能しない場合は、この設定を試して MTU を制限してください (有効範囲 20-512)</string>
<string name="pref_title_pebble_enable_applogs">ウォッチアプリのログ記録を有効にする</string>
<string name="pref_summary_pebble_enable_applogs">Gadgetbridgeがウォッチアプリからログを記録するようにする (再接続が必要です)</string>
<string name="pref_title_pebble_always_ack_pebblekit">早期 ACK PebbleKit</string>
<string name="pref_summary_pebble_always_ack_pebblekit">外部のサードパーティアプリケーションに送信されたメッセージは、常に即座に承認されます</string>
<string name="pref_title_pebble_reconnect_attempts">再接続の試行</string>
<string name="pref_title_unit_system">単位</string>
<string name="pref_title_timeformat">時刻形式</string>
<string name="pref_title_screentime">画面オンの期間</string>
<string name="prefs_title_all_day_heart_rate">一日中心拍数測定</string>
<string name="preferences_hplus_settings">HPlus/Makibes 設定</string>
<string name="not_connected">非接続</string>
<string name="connecting">接続中</string>
<string name="connected">接続</string>
@ -338,4 +354,11 @@ Mi Band、Pebble Health、Morpheuz からデータをインポートすること
<string name="title_activity_pebble_pairing">Pebbleペアリング</string>
<string name="pebble_pairing_hint">お使いのAndroidデバイスでペアリングのダイアログがポップアップすると思います。 起こらない場合は、通知ドロワーを調べて、ペアリング要求を受け入れます。 その後、Pebbleでペアリング要求を受け入れます</string>
<string name="weather_notification_label">天気予報アプリでこのスキンが有効になっていることを確認してください。\n\nここで必要な設定はありません。\n\nアプリ管理からPebbleのシステム天気アプリを有効にすることができます。\n\nサポートされているウォッチフェイスが自動的に天気を表示します。</string>
<string name="pref_title_setup_bt_pairing">Bluetoothペアリングを有効にする</string>
<string name="pref_summary_setup_bt_pairing">接続に問題がある場合は、この機能を無効にしてください</string>
<string name="unit_metric">メートル</string>
<string name="unit_imperial">ヤード・ポンド</string>
<string name="timeformat_24h">24時間</string>
<string name="timeformat_am_pm">AM/PM</string>
<string name="pref_screen_notification_profile_alarm_clock">アラームクロック</string>
</resources>

View File

@ -44,7 +44,6 @@
<string name="pref_title_notifications_repetitions">반복</string>
<string name="pref_title_notifications_call">전화</string>
<string name="pref_title_notifications_sms">SMS</string>
<string name="pref_title_notifications_k9mail">K-9 메일</string>
<string name="pref_title_notifications_pebblemsg">Pebble 메세지</string>
<string name="pref_title_notifications_generic">일반적인 알림 지원</string>
<string name="pref_title_whenscreenon">… 화면이 켜져있을 때도</string>

View File

@ -55,7 +55,6 @@
<string name="pref_title_notifications_repetitions">Powtórzenia</string>
<string name="pref_title_notifications_call">Połączenia</string>
<string name="pref_title_notifications_sms">SMS</string>
<string name="pref_title_notifications_k9mail">K9-Mail</string>
<string name="pref_title_notifications_pebblemsg">Wiadomości Pebble</string>
<string name="pref_title_notifications_generic">Obsługa ogólnych powiadomień</string>
<string name="pref_title_whenscreenon">... także gdy ekran jest włączony</string>

View File

@ -46,7 +46,6 @@
<string name="pref_title_notifications_repetitions">Повторы</string>
<string name="pref_title_notifications_call">Вызовы</string>
<string name="pref_title_notifications_sms">СМС-сообщения</string>
<string name="pref_title_notifications_k9mail">K9-Mail</string>
<string name="pref_title_notifications_pebblemsg">Сообщения Pebble</string>
<string name="pref_title_notifications_generic">Поддержка обычных уведомлений</string>
<string name="pref_title_whenscreenon">… даже когда экран включён</string>

View File

@ -44,7 +44,6 @@
<string name="pref_title_notifications_repetitions">Повтори</string>
<string name="pref_title_notifications_call">Виклики</string>
<string name="pref_title_notifications_sms">SMS—повідомлення</string>
<string name="pref_title_notifications_k9mail">K9-Mail</string>
<string name="pref_title_notifications_pebblemsg">Повідомлення Pebble</string>
<string name="pref_title_notifications_generic">Підтримка звичайних сповіщень</string>
<string name="pref_title_whenscreenon">… навіть коли екран увімкнено</string>

View File

@ -113,6 +113,19 @@
<item>3</item>
<item>1</item>
</string-array>
<string-array name="pebble_privacymode">
<item name="off">@string/pref_pebble_privacy_mode_off</item>
<item name="content">@string/pref_pebble_privacy_mode_content</item>
<item name="complete">@string/pref_pebble_privacy_mode_complete</item>
</string-array>
<string-array name="pebble_privacymode_values">
<item>@string/p_pebble_privacy_mode_off</item>
<item>@string/p_pebble_privacy_mode_content</item>
<item>@string/p_pebble_privacy_mode_complete</item>
</string-array>
<string-array name="mi2_dateformats">
<item>@string/dateformat_time</item>
<item>@string/dateformat_date_time</item>
@ -122,4 +135,36 @@
<item>@string/p_dateformat_datetime</item>
</string-array>
<string-array name="pref_entries_unit_system">
<item>@string/unit_metric</item>
<item>@string/unit_imperial</item>
</string-array>
<string-array name="pref_values_unit_system">
<item>@string/p_unit_metric</item>
<item>@string/p_unit_imperial</item>
</string-array>
<string-array name="pref_timeformat_entries">
<item>@string/timeformat_24h</item>
<item>@string/timeformat_am_pm</item>
</string-array>
<string-array name="pref_timeformat_values">
<item>@string/p_timeformat_24h</item>
<item>@string/p_timeformat_am_pm</item>
</string-array>
<string-array name="pref_call_privacy_mode">
<item name="off">@string/pref_call_privacy_mode_off</item>
<item name="name">@string/pref_call_privacy_mode_name</item>
<item name="complete">@string/pref_call_privacy_mode_complete</item>
</string-array>
<string-array name="pref_call_privacy_mode_values">
<item>@string/p_call_privacy_mode_off</item>
<item>@string/p_call_privacy_mode_name</item>
<item>@string/p_call_privacy_mode_complete</item>
</string-array>
</resources>

View File

@ -52,6 +52,7 @@
<string name="pref_header_general">General Settings</string>
<string name="pref_title_general_autoconnectonbluetooth">Connect to device when Bluetooth turned on</string>
<string name="pref_title_general_autostartonboot">Start automatically</string>
<string name="pref_title_general_autocreonnect">Reconnect automatically</string>
<string name="pref_title_audo_player">Preferred Audioplayer</string>
<string name="pref_default">Default</string>
@ -74,7 +75,6 @@
<string name="pref_title_notifications_repetitions">Repetitions</string>
<string name="pref_title_notifications_call">Phone Calls</string>
<string name="pref_title_notifications_sms">SMS</string>
<string name="pref_title_notifications_k9mail">K9-Mail</string>
<string name="pref_title_notifications_pebblemsg">Pebble Messages</string>
<string name="pref_summary_notifications_pebblemsg">Support for applications which send Notifications to the Pebble via PebbleKit.</string>
<string name="pref_title_notifications_generic">Generic notification support</string>
@ -88,6 +88,12 @@
<string name="when_screen_off">when screen is off</string>
<string name="never">never</string>
<string name="pref_header_privacy">Privacy</string>
<string name="pref_title_call_privacy_mode">Call Privacy Mode</string>
<string name="pref_call_privacy_mode_off">Display name and number</string>
<string name="pref_call_privacy_mode_name">Hide name but display number</string>
<string name="pref_call_privacy_mode_complete">Hide name and number</string>
<string name="pref_blacklist">Blacklist Apps</string>
<string name="pref_header_cannned_messages">Canned Messages</string>
@ -119,6 +125,11 @@
<string name="pref_title_autoremove_notifications">Autoremove dismissed Notifications</string>
<string name="pref_summary_autoremove_notifications">Notifications are automatically removed from the Pebble when dismissed from the Android device</string>
<string name="pref_title_pebble_privacy_mode">Privacy mode</string>
<string name="pref_pebble_privacy_mode_off">Normal notifications</string>
<string name="pref_pebble_privacy_mode_content">Shift the notification text off-screen</string>
<string name="pref_pebble_privacy_mode_complete">Show only the notification icon</string>
<string name="pref_header_location">Location</string>
<string name="pref_title_location_aquire">Acquire Location</string>
<string name="pref_title_location_latitude">Latitude</string>
@ -139,9 +150,18 @@
<string name="pref_summary_pebble_mtu_limit">If your Pebble 2/Pebble LE does not work as expected, try this setting to limit the MTU (valid range 20512)</string>
<string name="pref_title_pebble_enable_applogs">Enable Watch App Logging</string>
<string name="pref_summary_pebble_enable_applogs">Will cause logs from watch apps to be logged by Gadgetbridge (requires reconnect)</string>
<string name="pref_title_pebble_always_ack_pebblekit">Prematurely ACK PebbleKit</string>
<string name="pref_summary_pebble_always_ack_pebblekit">Will cause messages that are sent to external 3rd party apps to be acknowledged always and immediately</string>
<string name="pref_title_pebble_reconnect_attempts">Reconnection Attempts</string>
//HPlus Preferences
<string name="pref_title_unit_system">Units</string>
<string name="pref_title_timeformat">Time format</string>
<string name="pref_title_screentime">Screen on duration</string>
<string name="prefs_title_all_day_heart_rate">All day heart rate measurement</string>
<string name="preferences_hplus_settings">HPlus/Makibes Settings</string>
<string name="not_connected">not connected</string>
<string name="connecting">connecting</string>
<string name="connected">connected</string>
@ -257,6 +277,7 @@
<string name="notif_battery_low_bigtext_last_charge_time">Last charge: %s \n</string>
<string name="notif_battery_low_bigtext_number_of_charges">Number of charges: %s</string>
<string name="sleepchart_your_sleep">Your Sleep</string>
<string name="weeksleepchart_sleep_a_week">Sleep a week</string>
<string name="weekstepschart_steps_a_week">Steps a week</string>
<string name="activity_sleepchart_activity_and_sleep">Your Activity and Sleep</string>
<string name="updating_firmware">Updating Firmware…</string>
@ -330,23 +351,12 @@
<string name="charts_legend_heartrate">Heart Rate</string>
<string name="live_activity_heart_rate">Heart Rate</string>
<!-- Strings related to Onboading Activity -->
<string name="title_activity_onboarding">Database Import</string>
<string name="import_old_db_buttonlabel">Import old activity data</string>
<string name="import_old_db_information">Since Gadgetbridge 0.12.0 we use a new database format.
You are able to import old activity data and associate it with the device you are connecting to (%1$s).\n
\n
If you do not import old activity data now, you can still do it later by tapping the "MERGE OLD ACTIVITY DATA" button in the Database Management Activity"\n
\n
Please note that you can import data from Mi Band, Pebble Health and Morpheuz but NOT from Pebble Misfit.
</string>
<string name="pref_title_pebble_health_store_raw">Store raw record in the database</string>
<string name="pref_summary_pebble_health_store_raw">If checked the data is stored \"as is\" and is available for later interpretation. NB: the database will be bigger in this case!</string>
<string name="action_db_management">Database Management</string>
<string name="title_activity_db_management">Database Management</string>
<string name="activity_db_management_import_export_explanation">The database operations use the following path on your device. \nThis path is accessible to other Android applications and your computer. \nExpect to find your exported database (or place the database you want to import) there:</string>
<string name="activity_db_management_merge_old_explanation">The activity data recorded with Gadgetbridge versions prior to 0.12 must be converted to a new format. \nYou can do this using the button below. Be aware that you must be connected to the device you want to associate the old activity data with! \nIf you already imported your data and are happy with the result, you may delete the old database.</string>
<string name="activity_db_management_merge_old_title">Legacy Database Import / Delete</string>
<string name="activity_db_management_merge_old_title">Legacy Database Delete</string>
<string name="dbmanagementactivvity_cannot_access_export_path">Cannot access export path. Please contact the developers.</string>
<string name="dbmanagementactivity_exported_to">Exported to: %1$s</string>
<string name="dbmanagementactivity_error_exporting_db">"Error exporting DB: %1$s"</string>
@ -354,12 +364,6 @@
<string name="dbmanagementactivity_overwrite_database_confirmation">Really overwrite the current database? All your current activity data (if any) will be lost.</string>
<string name="dbmanagementactivity_import_successful">Import successful.</string>
<string name="dbmanagementactivity_error_importing_db">"Error importing DB: %1$s"</string>
<string name="dbmanagementactivity_no_old_activitydatabase_found">No old activity database found, nothing to import.</string>
<string name="dbmanagementactivity_no_connected_device">No connected device to associate old activity data with.</string>
<string name="dbmanagementactivity_merging_activity_data_title">Merging Activity Data</string>
<string name="dbmanagementactivity_please_wait_while_merging">Please wait while merging your activity data…</string>
<string name="dbmanagementactivity_error_importing_old_activity_data">Error importing old activity data into new database.</string>
<string name="dbmanagementactivity_associate_old_data_with_device">Associate old Data with Device</string>
<string name="dbmanagementactivity_delete_activity_data_title">Delete Activity Data?</string>
<string name="dbmanagementactivity_really_delete_entire_db">Really delete the entire database? All your activity data and information about your devices will be lost.</string>
<string name="dbmanagementactivity_database_successfully_deleted">Data successfully deleted.</string>
@ -382,4 +386,11 @@
<string name="weather_notification_label">Make sure that this skin is enabled in the Weather Notification app to get weather information on your Pebble.\n\nNo configuration is needed here.\n\nYou can enable the system weather app of your Pebble from the app management.\n\nSupported watchfaces will show the weather automatically.</string>
<string name="pref_title_setup_bt_pairing">Enable Bluetooth pairing</string>
<string name="pref_summary_setup_bt_pairing">Deactivate this if you have trouble connecting</string>
<string name="unit_metric">Metric</string>
<string name="unit_imperial">Imperial</string>
<string name="timeformat_24h">24H</string>
<string name="timeformat_am_pm">AM/PM</string>
<string name="pref_screen_notification_profile_alarm_clock">Alarm Clock</string>
</resources>

View File

@ -12,4 +12,17 @@
<item name="p_dateformat_time" type="string">dateformat_time</item>
<item name="p_dateformat_datetime" type="string">dateformat_datetime</item>
<item name="p_unit_metric" type="string">metric</item>
<item name="p_unit_imperial" type="string">imperial</item>
<item name="p_timeformat_24h" type="string">24h</item>
<item name="p_timeformat_am_pm" type="string">am/pm</item>
<item name="p_pebble_privacy_mode_off" type="string">off</item>
<item name="p_pebble_privacy_mode_content" type="string">content</item>
<item name="p_pebble_privacy_mode_complete" type="string">complete</item>
<item name="p_call_privacy_mode_off" type="string">off</item>
<item name="p_call_privacy_mode_name" type="string">name</item>
<item name="p_call_privacy_mode_complete" type="string">complete</item>
</resources>

View File

@ -1,5 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<changelog>
<release version="0.17.5" versioncode="86">
<change>Automatically start the service on boot (can be turned off)</change>
<change>Pebble: PebbleKit compatibility improvements (Datalogging)</change>
<change>Pebble: Display music shuffle and repeat states for some players</change>
<change>Pebble 2/LE: Speed up data transfer</change>
</release>
<release version="0.17.4" versioncode="85">
<change>Better integration with android music players</change>
<change>Privacy options for calls (hide caller name/number)</change>
<change>Send a notification to the connected if the Android Alarm Clock rings (com.android.deskclock)</change>
<change>Fixes for cyrillic transliteration</change>
<change>Pebble: Implement notification privacy modes</change>
<change>Pebble: Support weather for Obisdian watchface</change>
<change>Pebble: add a dev option to always and immediately ACK PebbleKit messages to the watch</change>
<change>HPlus: Support alarms</change>
<change>HPlus: Fix time and date sync and time format (12/24)</change>
<change>HPlus: Add device specific preferences and icon</change>
<change>HPlus: Support for Makibes F68</change>
</release>
<release version="0.17.3" versioncode="84">
<change>HPlus: Improve display of new messages and phone calls</change>
<change>HPlus: Fix bug related to steps and heart rate</change>

View File

@ -210,6 +210,36 @@
android:title="@string/vibration_try"/>
</PreferenceScreen>
<PreferenceScreen
android:key="vibration_profile_key"
android:title="@string/pref_screen_notification_profile_alarm_clock"
android:persistent="false">
<!-- workaround for missing toolbar -->
<PreferenceCategory
android:title="@string/pref_screen_notification_profile_alarm_clock"
/>
<ListPreference
android:defaultValue="@string/p_alarm_clock"
android:entries="@array/vibration_profile"
android:entryValues="@array/vibration_profile_values"
android:key="mi_vibration_profile_alarm_clock"
android:title="@string/miband_prefs_vibration"
android:summary="%s" />
<EditTextPreference
android:defaultValue="3"
android:inputType="number"
android:key="mi_vibration_count_alarm_clock"
android:maxLength="2"
android:title="@string/pref_title_notifications_repetitions" />
<Preference
android:key="mi_try_generic_alarm_clock"
android:persistent="false"
android:title="@string/vibration_try"/>
</PreferenceScreen>
<PreferenceScreen
android:key="vibration_profile_key"
android:title="@string/pref_screen_notification_profile_generic_navigation"

View File

@ -3,6 +3,10 @@
<PreferenceCategory
android:key="pref_key_general"
android:title="@string/pref_header_general">
<CheckBoxPreference
android:defaultValue="true"
android:key="general_autostartonboot"
android:title="@string/pref_title_general_autostartonboot" />
<CheckBoxPreference
android:defaultValue="false"
android:key="general_autoconnectonbluetooth"
@ -36,6 +40,44 @@
android:summaryOff="@string/pref_summary_minimize_priority_off"
android:summaryOn="@string/pref_summary_minimize_priority_on"
android:title="@string/pref_title_minimize_priority" />
<PreferenceScreen
android:key="pref_category_activity_personal"
android:title="@string/activity_prefs_about_you">
<EditTextPreference
android:inputType="number"
android:key="activity_user_year_of_birth"
android:maxLength="4"
android:title="@string/activity_prefs_year_birth" />
<ListPreference
android:defaultValue="2"
android:entries="@array/gender"
android:entryValues="@array/gender_values"
android:key="activity_user_gender"
android:title="@string/activity_prefs_gender"
android:summary="%s" />
<!--TODO: support localized heights and weights -->
<EditTextPreference
android:inputType="number"
android:key="activity_user_height_cm"
android:maxLength="3"
android:title="@string/activity_prefs_height_cm" />
<EditTextPreference
android:inputType="number"
android:key="activity_user_weight_kg"
android:maxLength="3"
android:title="@string/activity_prefs_weight_kg" />
<EditTextPreference
android:inputType="number"
android:key="activity_user_sleep_duration"
android:maxLength="2"
android:title="@string/activity_prefs_sleep_duration" />
</PreferenceScreen>
</PreferenceCategory>
<PreferenceCategory
android:key="pref_key_datetime"
@ -66,14 +108,6 @@
android:title="@string/pref_title_notifications_sms"
android:summary="%s" />
<ListPreference
android:defaultValue="when_screen_off"
android:entries="@array/notification_mode"
android:entryValues="@array/notification_mode_values"
android:key="notification_mode_k9mail"
android:title="@string/pref_title_notifications_k9mail"
android:summary="%s" />
<ListPreference
android:defaultValue="when_screen_off"
android:entries="@array/notification_mode"
@ -109,43 +143,16 @@
android:title="@string/pref_blacklist" />
</PreferenceCategory>
<PreferenceScreen
android:key="pref_category_activity_personal"
android:title="@string/activity_prefs_about_you">
<EditTextPreference
android:inputType="number"
android:key="activity_user_year_of_birth"
android:maxLength="4"
android:title="@string/activity_prefs_year_birth" />
<ListPreference
android:defaultValue="2"
android:entries="@array/gender"
android:entryValues="@array/gender_values"
android:key="activity_user_gender"
android:title="@string/activity_prefs_gender"
android:summary="%s" />
<!--TODO: support localized heights and weights -->
<EditTextPreference
android:inputType="number"
android:key="activity_user_height_cm"
android:maxLength="3"
android:title="@string/activity_prefs_height_cm" />
<EditTextPreference
android:inputType="number"
android:key="activity_user_weight_kg"
android:maxLength="3"
android:title="@string/activity_prefs_weight_kg" />
<EditTextPreference
android:inputType="number"
android:key="activity_user_sleep_duration"
android:maxLength="2"
android:title="@string/activity_prefs_sleep_duration" />
</PreferenceScreen>
<PreferenceCategory
android:title="@string/pref_header_privacy">
<ListPreference
android:key="pref_call_privacy_mode"
android:title="@string/pref_title_call_privacy_mode"
android:entries="@array/pref_call_privacy_mode"
android:entryValues="@array/pref_call_privacy_mode_values"
android:defaultValue="@string/pref_call_privacy_mode_off"
android:summary="%s" />
</PreferenceCategory>
<PreferenceCategory
android:title="@string/preferences_category_device_specific_settings">
@ -184,6 +191,13 @@
android:key="autoremove_notifications"
android:summary="@string/pref_summary_autoremove_notifications"
android:title="@string/pref_title_autoremove_notifications" />
<ListPreference
android:key="pebble_pref_privacy_mode"
android:title="@string/pref_title_pebble_privacy_mode"
android:entries="@array/pebble_privacymode"
android:entryValues="@array/pebble_privacymode_values"
android:defaultValue="@string/p_pebble_privacy_mode_off"
android:summary="%s" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/pref_header_activitytrackers">
<ListPreference
@ -376,6 +390,11 @@
android:key="pebble_enable_applogs"
android:summary="@string/pref_summary_pebble_enable_applogs"
android:title="@string/pref_title_pebble_enable_applogs" />
<CheckBoxPreference
android:defaultValue="false"
android:key="pebble_always_ack_pebblekit"
android:summary="@string/pref_summary_pebble_always_ack_pebblekit"
android:title="@string/pref_title_pebble_always_ack_pebblekit" />
<EditTextPreference
android:digits="0123456789."
android:key="pebble_emu_addr"
@ -388,6 +407,51 @@
android:title="Emulator Port" />
</PreferenceCategory>
</PreferenceScreen>
<PreferenceScreen
android:icon="@drawable/ic_device_hplus"
android:key="pref_key_hplus"
android:title="@string/preferences_hplus_settings">
<PreferenceCategory
android:key="pref_category_hplus_general"
android:title="@string/pref_header_general">
<ListPreference
android:defaultValue="left"
android:entries="@array/wearside"
android:entryValues="@array/wearside_values"
android:key="hplus_wrist"
android:title="@string/miband_prefs_wearside"
android:summary="%s" />
<ListPreference
android:defaultValue="24h"
android:entries="@array/pref_timeformat_entries"
android:entryValues="@array/pref_timeformat_values"
android:key="hplus_timeformat"
android:title="@string/pref_title_timeformat"
android:summary="%s" />
<EditTextPreference
android:defaultValue="5"
android:key="hplus_screentime"
android:title="@string/pref_title_screentime"/>
<CheckBoxPreference
android:defaultValue="true"
android:key="hplus_alldayhr"
android:title="@string/prefs_title_all_day_heart_rate" />
<ListPreference
android:defaultValue="metric"
android:entries="@array/pref_entries_unit_system"
android:entryValues="@array/pref_values_unit_system"
android:key="hplus_unit"
android:title="@string/pref_title_unit_system"
android:summary="%s" />
</PreferenceCategory>
</PreferenceScreen>
</PreferenceCategory>
<PreferenceCategory