mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-08-25 00:30:37 +02:00
parent
b40c3ade85
commit
957d441859
@ -445,7 +445,8 @@
|
|||||||
android:resource="@xml/shared_paths" />
|
android:resource="@xml/shared_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
|
|
||||||
<receiver android:name=".SleepAlarmWidget">
|
<receiver android:name=".SleepAlarmWidget"
|
||||||
|
android:label="@string/appwidget_sleep_alarm_widget_label">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
<action android:name="nodomain.freeyourgadget.gadgetbridge.SLEEP_ALARM_WIDGET_CLICK" />
|
<action android:name="nodomain.freeyourgadget.gadgetbridge.SLEEP_ALARM_WIDGET_CLICK" />
|
||||||
@ -456,6 +457,26 @@
|
|||||||
android:resource="@xml/sleep_alarm_widget_info" />
|
android:resource="@xml/sleep_alarm_widget_info" />
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".Widget"
|
||||||
|
android:label="@string/widget_listing_label">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||||
|
<action android:name="nodomain.freeyourgadget.gadgetbridge.WidgetClick" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.appwidget.provider"
|
||||||
|
android:resource="@xml/widget_info" />
|
||||||
|
</receiver>
|
||||||
|
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".activities.WidgetAlarmsActivity"
|
||||||
|
android:launchMode="singleInstance"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.Dialog"
|
||||||
|
android:excludeFromRecents="true"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:allowTaskReparenting="true"
|
android:allowTaskReparenting="true"
|
||||||
|
@ -0,0 +1,226 @@
|
|||||||
|
/* Copyright (C) 2016-2019 0nse, Andreas Shimokawa, Carsten Pfeiffer
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge;
|
||||||
|
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.appwidget.AppWidgetManager;
|
||||||
|
import android.appwidget.AppWidgetProvider;
|
||||||
|
import android.content.BroadcastReceiver;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.widget.RemoteViews;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.WidgetAlarmsActivity;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.DailyTotals;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
|
||||||
|
public class Widget extends AppWidgetProvider {
|
||||||
|
public static final String WIDGET_CLICK = "nodomain.freeyourgadget.gadgetbridge.WidgetClick";
|
||||||
|
public static final String NEW_DATA_ACTION = "nodomain.freeyourgadget.gadgetbridge.NewDataTrigger";
|
||||||
|
public static final String APPWIDGET_DELETED = "nodomain.freeyourgadget.gadgetbridge.APPWIDGET_DELETED";
|
||||||
|
public static final String ACTION_DEVICE_CHANGED = "nodomain.freeyourgadget.gadgetbridge.gbdevice.action.device_changed";
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(Widget.class);
|
||||||
|
static BroadcastReceiver broadcastReceiver = null;
|
||||||
|
|
||||||
|
private GBDevice getSelectedDevice() {
|
||||||
|
|
||||||
|
Context context = GBApplication.getContext();
|
||||||
|
|
||||||
|
if (!(context instanceof GBApplication)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
GBApplication gbApp = (GBApplication) context;
|
||||||
|
|
||||||
|
return gbApp.getDeviceManager().getSelectedDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
private float[] getSteps() {
|
||||||
|
Context context = GBApplication.getContext();
|
||||||
|
Calendar day = GregorianCalendar.getInstance();
|
||||||
|
|
||||||
|
if (!(context instanceof GBApplication)) {
|
||||||
|
return new float[]{0, 0, 0};
|
||||||
|
}
|
||||||
|
DailyTotals ds = new DailyTotals();
|
||||||
|
return ds.getDailyTotalsForAllDevices(day);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getHM(long value) {
|
||||||
|
return DateTimeUtils.formatDurationHoursMinutes(value, TimeUnit.MINUTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
|
||||||
|
int appWidgetId) {
|
||||||
|
|
||||||
|
GBDevice device = getSelectedDevice();
|
||||||
|
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);
|
||||||
|
|
||||||
|
//onclick refresh
|
||||||
|
Intent intent = new Intent(context, Widget.class);
|
||||||
|
intent.setAction(WIDGET_CLICK);
|
||||||
|
PendingIntent refreshDataIntent = PendingIntent.getBroadcast(
|
||||||
|
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
//views.setOnClickPendingIntent(R.id.todaywidget_bottom_layout, refreshDataIntent);
|
||||||
|
views.setOnClickPendingIntent(R.id.todaywidget_header_bar, refreshDataIntent);
|
||||||
|
|
||||||
|
//open GB main window
|
||||||
|
Intent startMainIntent = new Intent(context, ControlCenterv2.class);
|
||||||
|
PendingIntent startMainPIntent = PendingIntent.getActivity(context, 0, startMainIntent, 0);
|
||||||
|
views.setOnClickPendingIntent(R.id.todaywidget_header_icon, startMainPIntent);
|
||||||
|
|
||||||
|
//alarms popup menu
|
||||||
|
Intent startAlarmListIntent = new Intent(context, WidgetAlarmsActivity.class);
|
||||||
|
PendingIntent startAlarmListPIntent = PendingIntent.getActivity(context, 0, startAlarmListIntent, 0);
|
||||||
|
views.setOnClickPendingIntent(R.id.todaywidget_header_plus, startAlarmListPIntent);
|
||||||
|
|
||||||
|
//charts, requires device
|
||||||
|
if (device != null) {
|
||||||
|
Intent startChartsIntent = new Intent(context, ChartsActivity.class);
|
||||||
|
startChartsIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
||||||
|
PendingIntent startChartsPIntent = PendingIntent.getActivity(context, 0, startChartsIntent, 0);
|
||||||
|
views.setOnClickPendingIntent(R.id.todaywidget_bottom_layout, startChartsPIntent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float[] DailyTotals = getSteps();
|
||||||
|
|
||||||
|
views.setTextViewText(R.id.todaywidget_steps, context.getString(R.string.widget_steps_label, (int) DailyTotals[0]));
|
||||||
|
views.setTextViewText(R.id.todaywidget_sleep, context.getString(R.string.widget_sleep_label, getHM((long) DailyTotals[1])));
|
||||||
|
|
||||||
|
if (device != null) {
|
||||||
|
String status = String.format("%1s", device.getStateString());
|
||||||
|
if (device.isConnected()) {
|
||||||
|
if (device.getBatteryLevel() > 1) {
|
||||||
|
status = String.format("Battery %1s%%", device.getBatteryLevel());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
views.setTextViewText(R.id.todaywidget_device_status, status);
|
||||||
|
views.setTextViewText(R.id.todaywidget_device_name, device.getName());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instruct the widget manager to update the widget
|
||||||
|
appWidgetManager.updateAppWidget(appWidgetId, views);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void refreshData() {
|
||||||
|
Context context = GBApplication.getContext();
|
||||||
|
GBDevice device = getSelectedDevice();
|
||||||
|
|
||||||
|
if (device == null || !device.isInitialized()) {
|
||||||
|
GB.toast(context,
|
||||||
|
context.getString(R.string.device_not_connected),
|
||||||
|
Toast.LENGTH_SHORT, GB.ERROR);
|
||||||
|
GBApplication.deviceService().connect();
|
||||||
|
GB.toast(context,
|
||||||
|
context.getString(R.string.connecting),
|
||||||
|
Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
GB.toast(context,
|
||||||
|
context.getString(R.string.busy_task_fetch_activity_data),
|
||||||
|
Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
|
||||||
|
GBApplication.deviceService().onFetchRecordedData(RecordedDataTypes.TYPE_ACTIVITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateWidget() {
|
||||||
|
Context context = GBApplication.getContext();
|
||||||
|
|
||||||
|
AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);
|
||||||
|
ComponentName thisAppWidget = new ComponentName(context.getPackageName(), Widget.class.getName());
|
||||||
|
int[] appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidget);
|
||||||
|
|
||||||
|
onUpdate(context, appWidgetManager, appWidgetIds);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
|
||||||
|
// There may be multiple widgets active, so update all of them
|
||||||
|
for (int appWidgetId : appWidgetIds) {
|
||||||
|
updateAppWidget(context, appWidgetManager, appWidgetId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnabled(Context context) {
|
||||||
|
if (this.broadcastReceiver == null) {
|
||||||
|
LOG.debug("gbwidget BROADCAST receiver initialized.");
|
||||||
|
this.broadcastReceiver = new BroadcastReceiver() {
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
LOG.debug("gbwidget BROADCAST, action" + intent.getAction());
|
||||||
|
updateWidget();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
IntentFilter intentFilter = new IntentFilter();
|
||||||
|
intentFilter.addAction(NEW_DATA_ACTION);
|
||||||
|
intentFilter.addAction(ACTION_DEVICE_CHANGED);
|
||||||
|
LocalBroadcastManager.getInstance(context).registerReceiver(this.broadcastReceiver, intentFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisabled(Context context) {
|
||||||
|
|
||||||
|
if (this.broadcastReceiver != null) {
|
||||||
|
LocalBroadcastManager.getInstance(context).unregisterReceiver(this.broadcastReceiver);
|
||||||
|
this.broadcastReceiver=null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
super.onReceive(context, intent);
|
||||||
|
LOG.debug("gbwidget LOCAL onReceive, action: " + intent.getAction());
|
||||||
|
//this handles widget re-connection after apk updates
|
||||||
|
if (WIDGET_CLICK.equals(intent.getAction())) {
|
||||||
|
if (this.broadcastReceiver == null) {
|
||||||
|
onEnabled(context);
|
||||||
|
}
|
||||||
|
refreshData();
|
||||||
|
//updateWidget();
|
||||||
|
} else if (APPWIDGET_DELETED.equals(intent.getAction())) {
|
||||||
|
onDisabled(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,127 @@
|
|||||||
|
package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.GregorianCalendar;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.AlarmUtils;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
public class WidgetAlarmsActivity extends Activity implements View.OnClickListener {
|
||||||
|
|
||||||
|
TextView textView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
Context appContext = this.getApplicationContext();
|
||||||
|
if (appContext instanceof GBApplication) {
|
||||||
|
GBApplication gbApp = (GBApplication) appContext;
|
||||||
|
GBDevice selectedDevice = gbApp.getDeviceManager().getSelectedDevice();
|
||||||
|
if (selectedDevice == null || !selectedDevice.isInitialized()) {
|
||||||
|
GB.toast(this,
|
||||||
|
this.getString(R.string.not_connected),
|
||||||
|
Toast.LENGTH_LONG, GB.WARN);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
setContentView(R.layout.widget_alarms_activity_list);
|
||||||
|
int userSleepDuration = new ActivityUser().getSleepDuration();
|
||||||
|
textView = findViewById(R.id.alarm5);
|
||||||
|
if (userSleepDuration > 0) {
|
||||||
|
textView.setText(String.format(this.getString(R.string.widget_alarm_target_hours), userSleepDuration));
|
||||||
|
} else {
|
||||||
|
textView.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
|
||||||
|
switch (v.getId()) {
|
||||||
|
case R.id.alarm1:
|
||||||
|
setAlarm(5);
|
||||||
|
break;
|
||||||
|
case R.id.alarm2:
|
||||||
|
setAlarm(10);
|
||||||
|
break;
|
||||||
|
case R.id.alarm3:
|
||||||
|
setAlarm(20);
|
||||||
|
break;
|
||||||
|
case R.id.alarm4:
|
||||||
|
setAlarm(60);
|
||||||
|
break;
|
||||||
|
case R.id.alarm5:
|
||||||
|
setAlarm(0);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//this is to prevent screen flashing during closing
|
||||||
|
new Handler().postDelayed(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}, 150);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlarm(int duration) {
|
||||||
|
// current timestamp
|
||||||
|
GregorianCalendar calendar = new GregorianCalendar();
|
||||||
|
if (duration > 0) {
|
||||||
|
calendar.add(Calendar.MINUTE, duration);
|
||||||
|
} else {
|
||||||
|
int userSleepDuration = new ActivityUser().getSleepDuration();
|
||||||
|
// add preferred sleep duration
|
||||||
|
if (userSleepDuration > 0) {
|
||||||
|
calendar.add(Calendar.HOUR_OF_DAY, userSleepDuration);
|
||||||
|
} else { // probably testing
|
||||||
|
calendar.add(Calendar.MINUTE, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// overwrite the first alarm and activate it, without
|
||||||
|
|
||||||
|
Context appContext = this.getApplicationContext();
|
||||||
|
if (appContext instanceof GBApplication) {
|
||||||
|
GBApplication gbApp = (GBApplication) appContext;
|
||||||
|
GBDevice selectedDevice = gbApp.getDeviceManager().getSelectedDevice();
|
||||||
|
if (selectedDevice == null || !selectedDevice.isInitialized()) {
|
||||||
|
GB.toast(this,
|
||||||
|
this.getString(R.string.appwidget_not_connected),
|
||||||
|
Toast.LENGTH_LONG, GB.WARN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int hours = calendar.get(Calendar.HOUR_OF_DAY);
|
||||||
|
int minutes = calendar.get(Calendar.MINUTE);
|
||||||
|
|
||||||
|
GB.toast(this,
|
||||||
|
this.getString(R.string.appwidget_setting_alarm, hours, minutes),
|
||||||
|
Toast.LENGTH_SHORT, GB.INFO);
|
||||||
|
|
||||||
|
Alarm alarm = AlarmUtils.createSingleShot(0, true, calendar);
|
||||||
|
ArrayList<Alarm> alarms = new ArrayList<>(1);
|
||||||
|
alarms.add(alarm);
|
||||||
|
GBApplication.deviceService().onSetAlarms(alarms);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -28,15 +28,15 @@ import nodomain.freeyourgadget.gadgetbridge.model.ActivityAmounts;
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityKind;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
|
||||||
|
|
||||||
class ActivityAnalysis {
|
public class ActivityAnalysis {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(ActivityAnalysis.class);
|
public static final Logger LOG = LoggerFactory.getLogger(ActivityAnalysis.class);
|
||||||
|
|
||||||
// store raw steps and duration
|
// store raw steps and duration
|
||||||
protected HashMap<Integer, Long> stats = new HashMap<Integer, Long>();
|
protected HashMap<Integer, Long> stats = new HashMap<Integer, Long>();
|
||||||
// max speed determined from samples
|
// max speed determined from samples
|
||||||
private int maxSpeed = 0;
|
private int maxSpeed = 0;
|
||||||
|
|
||||||
ActivityAmounts calculateActivityAmounts(List<? extends ActivitySample> samples) {
|
public ActivityAmounts calculateActivityAmounts(List<? extends ActivitySample> samples) {
|
||||||
ActivityAmount deepSleep = new ActivityAmount(ActivityKind.TYPE_DEEP_SLEEP);
|
ActivityAmount deepSleep = new ActivityAmount(ActivityKind.TYPE_DEEP_SLEEP);
|
||||||
ActivityAmount lightSleep = new ActivityAmount(ActivityKind.TYPE_LIGHT_SLEEP);
|
ActivityAmount lightSleep = new ActivityAmount(ActivityKind.TYPE_LIGHT_SLEEP);
|
||||||
ActivityAmount notWorn = new ActivityAmount(ActivityKind.TYPE_NOT_WORN);
|
ActivityAmount notWorn = new ActivityAmount(ActivityKind.TYPE_NOT_WORN);
|
||||||
|
@ -0,0 +1,155 @@
|
|||||||
|
/* Copyright (C) 2017-2019 Carsten Pfeiffer, Daniele Gobbetti
|
||||||
|
|
||||||
|
This file is part of Gadgetbridge.
|
||||||
|
|
||||||
|
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as published
|
||||||
|
by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
Gadgetbridge is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||||
|
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ActivityAnalysis;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.entities.AbstractActivitySample;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||||
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public class DailyTotals {
|
||||||
|
Logger LOG = LoggerFactory.getLogger(DailyTotals.class);
|
||||||
|
|
||||||
|
|
||||||
|
public float[] getDailyTotalsForAllDevices(Calendar day) {
|
||||||
|
Context context = GBApplication.getContext();
|
||||||
|
//get today's steps for all devices in GB
|
||||||
|
float all_steps = 0;
|
||||||
|
float all_sleep = 0;
|
||||||
|
|
||||||
|
|
||||||
|
if (context instanceof GBApplication) {
|
||||||
|
GBApplication gbApp = (GBApplication) context;
|
||||||
|
List<? extends GBDevice> devices = gbApp.getDeviceManager().getDevices();
|
||||||
|
for (GBDevice device : devices) {
|
||||||
|
float[] all_daily = getDailyTotalsForDevice(device, day);
|
||||||
|
all_steps += all_daily[0];
|
||||||
|
all_sleep += all_daily[1] + all_daily[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.debug("gbwidget daily totals, all steps:" + all_steps);
|
||||||
|
LOG.debug("gbwidget daily totals, all sleep:" + all_sleep);
|
||||||
|
return new float[]{all_steps, all_sleep};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public float[] getDailyTotalsForDevice(GBDevice device, Calendar day
|
||||||
|
) {
|
||||||
|
|
||||||
|
try (DBHandler handler = GBApplication.acquireDB()) {
|
||||||
|
ActivityAnalysis analysis = new ActivityAnalysis();
|
||||||
|
ActivityAmounts amountsSteps = null;
|
||||||
|
ActivityAmounts amountsSleep = null;
|
||||||
|
|
||||||
|
amountsSteps = analysis.calculateActivityAmounts(getSamplesOfDay(handler, day, 0, device));
|
||||||
|
amountsSleep = analysis.calculateActivityAmounts(getSamplesOfDay(handler, day, -12, device));
|
||||||
|
|
||||||
|
float[] Sleep = getTotalsSleepForActivityAmounts(amountsSleep);
|
||||||
|
float Steps = getTotalsStepsForActivityAmounts(amountsSteps);
|
||||||
|
|
||||||
|
return new float[]{Steps, Sleep[0], Sleep[1]};
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
|
||||||
|
GB.toast("Error loading activity summaries.", Toast.LENGTH_SHORT, GB.ERROR, e);
|
||||||
|
return new float[]{0, 0, 0};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private float[] getTotalsSleepForActivityAmounts(ActivityAmounts activityAmounts) {
|
||||||
|
long totalSecondsDeepSleep = 0;
|
||||||
|
long totalSecondsLightSleep = 0;
|
||||||
|
for (ActivityAmount amount : activityAmounts.getAmounts()) {
|
||||||
|
if (amount.getActivityKind() == ActivityKind.TYPE_DEEP_SLEEP) {
|
||||||
|
totalSecondsDeepSleep += amount.getTotalSeconds();
|
||||||
|
} else if (amount.getActivityKind() == ActivityKind.TYPE_LIGHT_SLEEP) {
|
||||||
|
totalSecondsLightSleep += amount.getTotalSeconds();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int totalMinutesDeepSleep = (int) (totalSecondsDeepSleep / 60);
|
||||||
|
int totalMinutesLightSleep = (int) (totalSecondsLightSleep / 60);
|
||||||
|
return new float[]{totalMinutesDeepSleep, totalMinutesLightSleep};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private float getTotalsStepsForActivityAmounts(ActivityAmounts activityAmounts) {
|
||||||
|
long totalSteps = 0;
|
||||||
|
float totalValue = 0;
|
||||||
|
|
||||||
|
for (ActivityAmount amount : activityAmounts.getAmounts()) {
|
||||||
|
totalSteps += amount.getTotalSteps();
|
||||||
|
}
|
||||||
|
|
||||||
|
float[] totalValues = new float[]{totalSteps};
|
||||||
|
|
||||||
|
for (int i = 0; i < totalValues.length; i++) {
|
||||||
|
float value = totalValues[i];
|
||||||
|
totalValue += value;
|
||||||
|
}
|
||||||
|
return totalValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private List<? extends ActivitySample> getSamplesOfDay(DBHandler db, Calendar day, int offsetHours, 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);
|
||||||
|
day.add(Calendar.HOUR, offsetHours);
|
||||||
|
|
||||||
|
startTs = (int) (day.getTimeInMillis() / 1000);
|
||||||
|
endTs = startTs + 24 * 60 * 60 - 1;
|
||||||
|
|
||||||
|
return getSamples(db, device, startTs, endTs);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected List<? extends ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||||
|
return getAllSamples(db, device, tsFrom, tsTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected SampleProvider<? extends AbstractActivitySample> getProvider(DBHandler db, GBDevice device) {
|
||||||
|
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
|
||||||
|
return coordinator.getSampleProvider(device, db.getDaoSession());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected List<? extends ActivitySample> getAllSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
|
||||||
|
SampleProvider<? extends ActivitySample> provider = getProvider(db, device);
|
||||||
|
return provider.getAllActivitySamples(tsFrom, tsTo);
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.huami.operations;
|
|||||||
|
|
||||||
import android.bluetooth.BluetoothGatt;
|
import android.bluetooth.BluetoothGatt;
|
||||||
import android.bluetooth.BluetoothGattCharacteristic;
|
import android.bluetooth.BluetoothGattCharacteristic;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@ -126,6 +127,8 @@ public abstract class AbstractFetchOperation extends AbstractHuamiOperation {
|
|||||||
GB.updateTransferNotification(null, "", false, 100, getContext());
|
GB.updateTransferNotification(null, "", false, 100, getContext());
|
||||||
operationFinished();
|
operationFinished();
|
||||||
unsetBusy();
|
unsetBusy();
|
||||||
|
Intent intent = new Intent("nodomain.freeyourgadget.gadgetbridge.NewDataTrigger");
|
||||||
|
getContext().sendBroadcast(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
BIN
app/src/main/res/drawable/widget_preview.png
Normal file
BIN
app/src/main/res/drawable/widget_preview.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
121
app/src/main/res/layout/widget.xml
Normal file
121
app/src/main/res/layout/widget.xml
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/alarm_list_top_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/primary_dark"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/todaywidget_header_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="34dp"
|
||||||
|
android:background="@color/accent"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/todaywidget_header_icon"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:adjustViewBounds="false"
|
||||||
|
android:contentDescription="@string/icon_placeholder"
|
||||||
|
android:cropToPadding="false"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:src="@drawable/ic_notification" />
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/todaywidget_header_bar"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="start"
|
||||||
|
android:paddingLeft="0dp"
|
||||||
|
android:paddingRight="0dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/todaywidget_device_name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:drawablePadding="4dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="@string/app_name"
|
||||||
|
android:textColor="@color/primarytext_dark"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/todaywidget_header_plus"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:contentDescription="@string/icon_placeholder"
|
||||||
|
android:padding="0dp"
|
||||||
|
android:src="@drawable/ic_watchface" />
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/todaywidget_bottom_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingLeft="3dp"
|
||||||
|
android:paddingRight="3dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/todaywidget_bottom_vertical"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/todaywidget_steps"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/widget_steps_label"
|
||||||
|
android:gravity="center_vertical|center_horizontal"
|
||||||
|
android:text="@string/widget_steps_label"
|
||||||
|
android:textColor="@color/primarytext_dark"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/todaywidget_sleep"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:contentDescription="@string/widget_sleep_label"
|
||||||
|
android:gravity="center_vertical|center_horizontal"
|
||||||
|
android:text="@string/widget_sleep_label"
|
||||||
|
android:textColor="@color/primarytext_dark"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/empty"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/todaywidget_device_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:foregroundGravity="right"
|
||||||
|
android:gravity="end"
|
||||||
|
android:text="@string/device_not_connected"
|
||||||
|
android:textColor="@color/primarytext_dark"
|
||||||
|
android:textSize="18sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
103
app/src/main/res/layout/widget_alarms_activity_list.xml
Normal file
103
app/src/main/res/layout/widget_alarms_activity_list.xml
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/alarm_list_top_layout"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/primary_dark"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/todaywidget_header_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:background="@color/accent"
|
||||||
|
android:baselineAligned="false"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/todaywidget_header_bar"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="start"
|
||||||
|
android:paddingLeft="0dp"
|
||||||
|
android:paddingRight="0dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/todaywidget_alarm_list_header"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginRight="8dp"
|
||||||
|
android:drawablePadding="4dp"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:text="@string/widget_set_alarm_after"
|
||||||
|
android:textColor="@color/primarytext_dark"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/todaywidget_alarm_list_bottom_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="10dp"
|
||||||
|
android:paddingRight="10dp">
|
||||||
|
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/alarm1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:onClick="onClick"
|
||||||
|
android:text="@string/widget_5_minutes"
|
||||||
|
android:textSize="30sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/alarm2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:onClick="onClick"
|
||||||
|
android:text="@string/widget_10_minutes"
|
||||||
|
android:textSize="30sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/alarm3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:onClick="onClick"
|
||||||
|
android:text="@string/widget_20_minutes"
|
||||||
|
android:textSize="30sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/alarm4"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:onClick="onClick"
|
||||||
|
android:text="@string/widget_1_hour"
|
||||||
|
android:textSize="30sp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/alarm5"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:onClick="onClick"
|
||||||
|
android:text="@string/miband_prefs_fitness_goal"
|
||||||
|
android:textSize="30sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
@ -527,10 +527,7 @@
|
|||||||
|
|
||||||
<string name="authenticating">Authenticating</string>
|
<string name="authenticating">Authenticating</string>
|
||||||
<string name="authentication_required">Authentication required</string>
|
<string name="authentication_required">Authentication required</string>
|
||||||
<string name="appwidget_text">Zzz</string>
|
|
||||||
<string name="add_widget">Add widget</string>
|
|
||||||
<string name="activity_prefs_sleep_duration">Preferred sleep duration in hours</string>
|
<string name="activity_prefs_sleep_duration">Preferred sleep duration in hours</string>
|
||||||
<string name="appwidget_setting_alarm">Setting alarm for %1$02d:%2$02d</string>
|
|
||||||
<string name="device_hw">Hardware revision: %1$s</string>
|
<string name="device_hw">Hardware revision: %1$s</string>
|
||||||
<string name="device_fw">Firmware version: %1$s</string>
|
<string name="device_fw">Firmware version: %1$s</string>
|
||||||
<string name="error_creating_directory_for_logfiles">Error creating directory for log files: %1$s</string>
|
<string name="error_creating_directory_for_logfiles">Error creating directory for log files: %1$s</string>
|
||||||
@ -715,5 +712,23 @@
|
|||||||
<string name="filter_mode">Filter Mode</string>
|
<string name="filter_mode">Filter Mode</string>
|
||||||
<string name="mode_configuration">Mode Configuration</string>
|
<string name="mode_configuration">Mode Configuration</string>
|
||||||
<string name="save_configuration">Save Configuration</string>
|
<string name="save_configuration">Save Configuration</string>
|
||||||
|
<!-- App Widgets-->
|
||||||
<string name="appwidget_not_connected">Not connected, alarm not set.</string>
|
<string name="appwidget_not_connected">Not connected, alarm not set.</string>
|
||||||
|
<string name="appwidget_text">Zzz</string>
|
||||||
|
<string name="add_widget">Add widget</string>
|
||||||
|
<string name="appwidget_setting_alarm">Setting alarm for %1$02d:%2$02d</string>
|
||||||
|
<string name="appwidget_sleep_alarm_widget_label">Sleep Alarm</string>
|
||||||
|
|
||||||
|
<string name="widget_steps_label">Steps: %1$02d</string>
|
||||||
|
<string name="widget_sleep_label">Sleep: %1$s</string>
|
||||||
|
<string name="widget_listing_label">Status and Alarms</string>
|
||||||
|
<string name="widget_set_alarm_after">Set alarm after:</string>
|
||||||
|
<string name="widget_5_minutes">5 minutes</string>
|
||||||
|
<string name="widget_10_minutes">10 minutes</string>
|
||||||
|
<string name="widget_20_minutes">20 minutes</string>
|
||||||
|
<string name="widget_1_hour">1 hour</string>
|
||||||
|
<string name="icon_placeholder" translatable="false">Icon</string>
|
||||||
|
<string name="widget_alarm_target_hours">%d hours</string>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
14
app/src/main/res/xml/widget_info.xml
Normal file
14
app/src/main/res/xml/widget_info.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:initialKeyguardLayout="@layout/widget"
|
||||||
|
android:initialLayout="@layout/widget"
|
||||||
|
android:minHeight="50dp"
|
||||||
|
android:minWidth="100dp"
|
||||||
|
minResizeHeight="5dp"
|
||||||
|
minResizeWidth="5dp"
|
||||||
|
android:resizeMode="horizontal|vertical"
|
||||||
|
android:previewImage="@drawable/widget_preview"
|
||||||
|
android:updatePeriodMillis="86400000"
|
||||||
|
android:widgetCategory="home_screen|keyguard|searchbox">
|
||||||
|
</appwidget-provider>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user