2020-01-09 10:44:32 +01:00
|
|
|
/* Copyright (C) 2019-2020 Andreas Shimokawa, vanous
|
2019-08-13 19:54:18 +02:00
|
|
|
|
|
|
|
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.
|
2020-12-07 22:13:49 +01:00
|
|
|
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/>. */
|
|
|
|
/* Copyright (C) 2019-2020 Andreas Shimokawa, vanous
|
|
|
|
|
|
|
|
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.
|
|
|
|
GNU Affero General Public License for more details.
|
2019-08-13 19:54:18 +02:00
|
|
|
|
|
|
|
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;
|
2020-12-07 22:13:49 +01:00
|
|
|
import android.os.Bundle;
|
|
|
|
import android.view.View;
|
2019-08-13 19:54:18 +02:00
|
|
|
import android.widget.RemoteViews;
|
|
|
|
import android.widget.Toast;
|
|
|
|
|
|
|
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
|
|
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
2020-12-07 22:13:49 +01:00
|
|
|
import java.text.DecimalFormat;
|
2019-08-13 19:54:18 +02:00
|
|
|
import java.util.Calendar;
|
|
|
|
import java.util.GregorianCalendar;
|
2020-12-07 22:13:49 +01:00
|
|
|
import java.util.List;
|
2019-08-13 19:54:18 +02:00
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2;
|
2021-01-23 21:42:12 +01:00
|
|
|
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
|
2019-08-13 19:54:18 +02:00
|
|
|
import nodomain.freeyourgadget.gadgetbridge.activities.WidgetAlarmsActivity;
|
|
|
|
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
|
|
|
|
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
2020-12-07 22:13:49 +01:00
|
|
|
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
|
2019-08-13 19:54:18 +02:00
|
|
|
import nodomain.freeyourgadget.gadgetbridge.model.DailyTotals;
|
|
|
|
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes;
|
2019-09-01 22:09:09 +02:00
|
|
|
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
|
2019-08-13 19:54:18 +02:00
|
|
|
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils;
|
|
|
|
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
2020-12-07 22:13:49 +01:00
|
|
|
import nodomain.freeyourgadget.gadgetbridge.util.WidgetPreferenceStorage;
|
2019-08-13 19:54:18 +02:00
|
|
|
|
|
|
|
public class Widget extends AppWidgetProvider {
|
|
|
|
public static final String WIDGET_CLICK = "nodomain.freeyourgadget.gadgetbridge.WidgetClick";
|
2020-12-07 22:13:49 +01:00
|
|
|
public static final String APPWIDGET_DELETED = "android.appwidget.action.APPWIDGET_DELETED";
|
|
|
|
|
2019-08-13 19:54:18 +02:00
|
|
|
private static final Logger LOG = LoggerFactory.getLogger(Widget.class);
|
|
|
|
static BroadcastReceiver broadcastReceiver = null;
|
2020-12-07 22:13:49 +01:00
|
|
|
GBDevice selectedDevice;
|
2019-08-13 19:54:18 +02:00
|
|
|
|
|
|
|
private GBDevice getSelectedDevice() {
|
|
|
|
Context context = GBApplication.getContext();
|
|
|
|
if (!(context instanceof GBApplication)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
GBApplication gbApp = (GBApplication) context;
|
|
|
|
return gbApp.getDeviceManager().getSelectedDevice();
|
|
|
|
}
|
|
|
|
|
2020-12-07 22:13:49 +01:00
|
|
|
private GBDevice getDeviceByMAC(Context appContext, String HwAddress) {
|
|
|
|
GBApplication gbApp = (GBApplication) appContext;
|
|
|
|
List<? extends GBDevice> devices = gbApp.getDeviceManager().getDevices();
|
|
|
|
for (GBDevice device : devices) {
|
|
|
|
if (device.getAddress().equals(HwAddress)) {
|
|
|
|
return device;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-02-26 22:37:18 +01:00
|
|
|
private long[] getSteps() {
|
2019-08-13 19:54:18 +02:00
|
|
|
Context context = GBApplication.getContext();
|
|
|
|
Calendar day = GregorianCalendar.getInstance();
|
|
|
|
|
|
|
|
if (!(context instanceof GBApplication)) {
|
2020-02-26 22:37:18 +01:00
|
|
|
return new long[]{0, 0, 0};
|
2019-08-13 19:54:18 +02:00
|
|
|
}
|
|
|
|
DailyTotals ds = new DailyTotals();
|
2020-12-07 22:13:49 +01:00
|
|
|
return ds.getDailyTotalsForDevice(selectedDevice, day);
|
|
|
|
//return ds.getDailyTotalsForAllDevices(day);
|
2019-08-13 19:54:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private String getHM(long value) {
|
|
|
|
return DateTimeUtils.formatDurationHoursMinutes(value, TimeUnit.MINUTES);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void updateAppWidget(Context context, AppWidgetManager appWidgetManager,
|
|
|
|
int appWidgetId) {
|
|
|
|
|
2020-12-07 22:13:49 +01:00
|
|
|
selectedDevice = getSelectedDevice();
|
|
|
|
WidgetPreferenceStorage widgetPreferenceStorage = new WidgetPreferenceStorage();
|
|
|
|
String savedDeviceAddress = widgetPreferenceStorage.getSavedDeviceAddress(context, appWidgetId);
|
|
|
|
if (savedDeviceAddress != null) {
|
|
|
|
selectedDevice = getDeviceByMAC(context.getApplicationContext(), savedDeviceAddress); //this would probably only happen if device no longer exists in GB
|
|
|
|
}
|
|
|
|
|
2019-08-13 19:54:18 +02:00
|
|
|
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget);
|
|
|
|
|
|
|
|
//onclick refresh
|
|
|
|
Intent intent = new Intent(context, Widget.class);
|
|
|
|
intent.setAction(WIDGET_CLICK);
|
2020-12-07 22:13:49 +01:00
|
|
|
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
|
2019-08-13 19:54:18 +02:00
|
|
|
PendingIntent refreshDataIntent = PendingIntent.getBroadcast(
|
2020-12-21 11:18:03 +01:00
|
|
|
context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
2020-12-07 22:13:49 +01:00
|
|
|
views.setOnClickPendingIntent(R.id.todaywidget_header_container, refreshDataIntent);
|
2019-08-13 19:54:18 +02:00
|
|
|
|
|
|
|
//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);
|
2020-12-21 11:18:03 +01:00
|
|
|
startAlarmListIntent.putExtra(GBDevice.EXTRA_DEVICE, selectedDevice);
|
|
|
|
PendingIntent startAlarmListPIntent = PendingIntent.getActivity(context, appWidgetId, startAlarmListIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
2020-12-07 22:13:49 +01:00
|
|
|
views.setOnClickPendingIntent(R.id.todaywidget_header_alarm_icon, startAlarmListPIntent);
|
2019-08-13 19:54:18 +02:00
|
|
|
|
2020-12-21 11:18:03 +01:00
|
|
|
//charts
|
|
|
|
Intent startChartsIntent = new Intent(context, ChartsActivity.class);
|
|
|
|
startChartsIntent.putExtra(GBDevice.EXTRA_DEVICE, selectedDevice);
|
|
|
|
PendingIntent startChartsPIntent = PendingIntent.getActivity(context, appWidgetId, startChartsIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
|
|
|
views.setOnClickPendingIntent(R.id.todaywidget_bottom_layout, startChartsPIntent);
|
2019-08-13 19:54:18 +02:00
|
|
|
|
2020-02-26 22:37:18 +01:00
|
|
|
long[] dailyTotals = getSteps();
|
2020-12-07 22:13:49 +01:00
|
|
|
int steps = (int) dailyTotals[0];
|
|
|
|
int sleep = (int) dailyTotals[1];
|
|
|
|
ActivityUser activityUser = new ActivityUser();
|
|
|
|
int stepGoal = activityUser.getStepsGoal();
|
|
|
|
int sleepGoal = activityUser.getSleepDuration();
|
|
|
|
int sleepGoalMinutes = sleepGoal * 60;
|
|
|
|
int distanceGoal = activityUser.getDistanceMeters() * 100;
|
|
|
|
int stepLength = activityUser.getStepLengthCm();
|
2021-01-23 21:42:12 +01:00
|
|
|
double distanceMeters = dailyTotals[0] * stepLength / 100;
|
|
|
|
double distanceFeet = distanceMeters * 3.28084f;
|
|
|
|
double distanceFormatted = 0;
|
2020-12-07 22:13:49 +01:00
|
|
|
|
|
|
|
String unit = "###m";
|
2021-01-23 21:42:12 +01:00
|
|
|
distanceFormatted = distanceMeters;
|
|
|
|
if (distanceMeters > 2000) {
|
|
|
|
distanceFormatted = distanceMeters / 1000;
|
2020-12-07 22:13:49 +01:00
|
|
|
unit = "###.#km";
|
|
|
|
}
|
2021-01-23 21:42:12 +01:00
|
|
|
String units = GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, GBApplication.getContext().getString(R.string.p_unit_metric));
|
|
|
|
if (units.equals(GBApplication.getContext().getString(R.string.p_unit_imperial))) {
|
|
|
|
unit = "###ft";
|
|
|
|
distanceFormatted = distanceFeet;
|
|
|
|
if (distanceFeet > 6000) {
|
|
|
|
distanceFormatted = distanceFeet * 0.0001893939f;
|
|
|
|
unit = "###.#mi";
|
|
|
|
}
|
|
|
|
}
|
2020-12-07 22:13:49 +01:00
|
|
|
DecimalFormat df = new DecimalFormat(unit);
|
|
|
|
|
|
|
|
views.setTextViewText(R.id.todaywidget_steps, String.format("%1s", steps));
|
|
|
|
views.setTextViewText(R.id.todaywidget_sleep, String.format("%1s", getHM(sleep)));
|
|
|
|
views.setTextViewText(R.id.todaywidget_distance, df.format(distanceFormatted));
|
|
|
|
views.setProgressBar(R.id.todaywidget_steps_progress, stepGoal, steps, false);
|
|
|
|
views.setProgressBar(R.id.todaywidget_sleep_progress, sleepGoalMinutes, sleep, false);
|
|
|
|
views.setProgressBar(R.id.todaywidget_distance_progress, distanceGoal, steps * stepLength, false);
|
|
|
|
views.setViewVisibility(R.id.todaywidget_battery_icon, View.GONE);
|
|
|
|
if (selectedDevice != null) {
|
|
|
|
String status = String.format("%1s", selectedDevice.getStateString());
|
|
|
|
if (selectedDevice.isConnected()) {
|
|
|
|
if (selectedDevice.getBatteryLevel() > 1) {
|
|
|
|
views.setViewVisibility(R.id.todaywidget_battery_icon, View.VISIBLE);
|
2021-02-14 16:46:57 +01:00
|
|
|
|
2020-12-07 22:13:49 +01:00
|
|
|
status = String.format("%1s%%", selectedDevice.getBatteryLevel());
|
2019-08-13 19:54:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-07 22:13:49 +01:00
|
|
|
String deviceName = selectedDevice.getAlias() != null ? selectedDevice.getAlias() : selectedDevice.getName();
|
2019-08-13 19:54:18 +02:00
|
|
|
views.setTextViewText(R.id.todaywidget_device_status, status);
|
2020-12-07 22:13:49 +01:00
|
|
|
views.setTextViewText(R.id.todaywidget_device_name, deviceName);
|
2019-08-13 19:54:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
2020-12-07 22:13:49 +01:00
|
|
|
}
|
2019-08-13 19:54:18 +02:00
|
|
|
|
2020-12-07 22:13:49 +01:00
|
|
|
public void removeWidget(Context context, int appWidgetId) {
|
|
|
|
WidgetPreferenceStorage widgetPreferenceStorage = new WidgetPreferenceStorage();
|
|
|
|
widgetPreferenceStorage.removeWidgetById(context, appWidgetId);
|
2019-08-13 19:54:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@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) {
|
2019-09-01 22:09:09 +02:00
|
|
|
if (broadcastReceiver == null) {
|
2019-08-13 19:54:18 +02:00
|
|
|
LOG.debug("gbwidget BROADCAST receiver initialized.");
|
2019-09-01 22:09:09 +02:00
|
|
|
broadcastReceiver = new BroadcastReceiver() {
|
2019-08-13 19:54:18 +02:00
|
|
|
@Override
|
|
|
|
public void onReceive(Context context, Intent intent) {
|
|
|
|
LOG.debug("gbwidget BROADCAST, action" + intent.getAction());
|
|
|
|
updateWidget();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
IntentFilter intentFilter = new IntentFilter();
|
2019-09-01 22:09:09 +02:00
|
|
|
intentFilter.addAction(GBApplication.ACTION_NEW_DATA);
|
|
|
|
intentFilter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
|
|
|
|
LocalBroadcastManager.getInstance(context).registerReceiver(broadcastReceiver, intentFilter);
|
2019-08-13 19:54:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onDisabled(Context context) {
|
2019-09-01 22:09:09 +02:00
|
|
|
if (broadcastReceiver != null) {
|
2020-12-07 22:13:49 +01:00
|
|
|
AndroidUtils.safeUnregisterBroadcastReceiver(context, broadcastReceiver);
|
2019-09-01 22:09:09 +02:00
|
|
|
broadcastReceiver = null;
|
2019-08-13 19:54:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onReceive(Context context, Intent intent) {
|
|
|
|
super.onReceive(context, intent);
|
2020-12-07 22:13:49 +01:00
|
|
|
LOG.debug("gbwidget LOCAL onReceive, action: " + intent.getAction() + intent);
|
|
|
|
Bundle extras = intent.getExtras();
|
|
|
|
int appWidgetId = -1;
|
|
|
|
if (extras != null) {
|
|
|
|
appWidgetId = extras.getInt(
|
|
|
|
AppWidgetManager.EXTRA_APPWIDGET_ID,
|
|
|
|
AppWidgetManager.INVALID_APPWIDGET_ID);
|
|
|
|
}
|
|
|
|
|
2019-08-13 19:54:18 +02:00
|
|
|
//this handles widget re-connection after apk updates
|
|
|
|
if (WIDGET_CLICK.equals(intent.getAction())) {
|
2019-09-01 22:09:09 +02:00
|
|
|
if (broadcastReceiver == null) {
|
2019-08-13 19:54:18 +02:00
|
|
|
onEnabled(context);
|
|
|
|
}
|
2020-12-07 22:13:49 +01:00
|
|
|
refreshData();
|
2019-08-13 19:54:18 +02:00
|
|
|
//updateWidget();
|
|
|
|
} else if (APPWIDGET_DELETED.equals(intent.getAction())) {
|
|
|
|
onDisabled(context);
|
2020-12-07 22:13:49 +01:00
|
|
|
removeWidget(context, appWidgetId);
|
2019-08-13 19:54:18 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|