Extract GBDeviceEvent to each individual event

This commit is contained in:
José Rebelo
2025-04-06 15:33:29 +01:00
parent 3dd88e3ea3
commit dafb5bcb11
35 changed files with 950 additions and 727 deletions
@@ -18,10 +18,120 @@
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public abstract class GBDeviceEvent {
private static final Logger LOG = LoggerFactory.getLogger(GBDeviceEvent.class);
@NonNull
@Override
public String toString() {
return getClass().getSimpleName() + ": ";
}
public abstract void evaluate(final Context context, final GBDevice device);
/**
* Helper method to run specific actions configured in the device preferences, upon wear state
* or awake/asleep events.
*
* @param actions
* @param message
*/
protected void handleDeviceAction(final Context context, final GBDevice device, Set<String> actions, String message) {
if (actions.isEmpty()) {
return;
}
LOG.debug("Handing device actions: {}", TextUtils.join(",", actions));
final String actionBroadcast = context.getString(R.string.pref_device_action_broadcast_value);
final String actionFitnessControlStart = context.getString(R.string.pref_device_action_fitness_app_control_start_value);
final String actionFitnessControlStop = context.getString(R.string.pref_device_action_fitness_app_control_stop_value);
final String actionFitnessControlToggle = context.getString(R.string.pref_device_action_fitness_app_control_toggle_value);
final String actionMediaPlay = context.getString(R.string.pref_media_play_value);
final String actionMediaPause = context.getString(R.string.pref_media_pause_value);
final String actionMediaPlayPause = context.getString(R.string.pref_media_playpause_value);
final String actionDndOff = context.getString(R.string.pref_device_action_dnd_off_value);
final String actionDndpriority = context.getString(R.string.pref_device_action_dnd_priority_value);
final String actionDndAlarms = context.getString(R.string.pref_device_action_dnd_alarms_value);
final String actionDndOn = context.getString(R.string.pref_device_action_dnd_on_value);
if (actions.contains(actionBroadcast)) {
if (message != null) {
Intent in = new Intent();
in.setAction(message);
LOG.info("Sending broadcast {}", message);
context.getApplicationContext().sendBroadcast(in);
}
}
if (actions.contains(actionFitnessControlStart)) {
OpenTracksController.startRecording(context);
} else if (actions.contains(actionFitnessControlStop)) {
OpenTracksController.stopRecording(context);
} else if (actions.contains(actionFitnessControlToggle)) {
OpenTracksController.toggleRecording(context);
}
final String mediaAction;
if (actions.contains(actionMediaPlayPause)) {
mediaAction = actionMediaPlayPause;
} else if (actions.contains(actionMediaPause)) {
mediaAction = actionMediaPause;
} else if (actions.contains(actionMediaPlay)) {
mediaAction = actionMediaPlay;
} else {
mediaAction = null;
}
if (mediaAction != null) {
GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.valueOf(mediaAction);
deviceEventMusicControl.evaluate(context, device);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
final int interruptionFilter;
if (actions.contains(actionDndOff)) {
interruptionFilter = NotificationManager.INTERRUPTION_FILTER_ALL;
} else if (actions.contains(actionDndpriority)) {
interruptionFilter = NotificationManager.INTERRUPTION_FILTER_PRIORITY;
} else if (actions.contains(actionDndAlarms)) {
interruptionFilter = NotificationManager.INTERRUPTION_FILTER_ALARMS;
} else if (actions.contains(actionDndOn)) {
interruptionFilter = NotificationManager.INTERRUPTION_FILTER_NONE;
} else {
interruptionFilter = NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
}
if (interruptionFilter != NotificationManager.INTERRUPTION_FILTER_UNKNOWN) {
LOG.debug("Setting do not disturb to {}", interruptionFilter);
if (!notificationManager.isNotificationPolicyAccessGranted()) {
LOG.warn("Do not disturb permissions not granted");
}
notificationManager.setInterruptionFilter(interruptionFilter);
}
}
}
}
@@ -16,9 +16,38 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import android.content.Intent;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AbstractAppManagerFragment;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
public class GBDeviceEventAppInfo extends GBDeviceEvent {
public GBDeviceApp apps[];
private static final Logger LOG = LoggerFactory.getLogger(GBDeviceEventAppInfo.class);
public GBDeviceApp[] apps;
public byte freeSlot = -1;
@Override
public void evaluate(final Context context, final GBDevice device) {
LOG.info("Got event for APP_INFO");
Intent appInfoIntent = new Intent(AbstractAppManagerFragment.ACTION_REFRESH_APPLIST);
int appCount = apps.length;
appInfoIntent.putExtra("app_count", appCount);
for (int i = 0; i < appCount; i++) {
appInfoIntent.putExtra("app_name" + i, apps[i].getName());
appInfoIntent.putExtra("app_creator" + i, apps[i].getCreator());
appInfoIntent.putExtra("app_version" + i, apps[i].getVersion());
appInfoIntent.putExtra("app_uuid" + i, apps[i].getUUID().toString());
appInfoIntent.putExtra("app_type" + i, apps[i].getType().ordinal());
}
LocalBroadcastManager.getInstance(context).sendBroadcast(appInfoIntent);
}
}
@@ -16,14 +16,23 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class GBDeviceEventAppManagement extends GBDeviceEvent {
public Event event = Event.UNKNOWN;
public EventType type = EventType.UNKNOWN;
public int token = -1;
public UUID uuid = null;
@Override
public void evaluate(final Context context, final GBDevice device) {
// FIXME: Pebble-specific, handled in support class
}
public enum EventType {
UNKNOWN,
INSTALL,
@@ -16,8 +16,14 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import androidx.annotation.NonNull;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class GBDeviceEventAppMessage extends GBDeviceEvent {
public static int TYPE_APPMESSAGE = 0;
public static int TYPE_ACK = 1;
@@ -28,6 +34,7 @@ public class GBDeviceEventAppMessage extends GBDeviceEvent {
public int id;
public String message;
@NonNull
@Override
public String toString() {
return "GBDeviceEventAppMessage{" +
@@ -37,4 +44,9 @@ public class GBDeviceEventAppMessage extends GBDeviceEvent {
", id=" + id +
'}';
}
@Override
public void evaluate(final Context context, final GBDevice device) {
// FIXME: Pebble-specific, handled in support class
}
}
@@ -18,11 +18,31 @@
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.DateFormat;
import java.util.GregorianCalendar;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.entities.BatteryLevel;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
public class GBDeviceEventBatteryInfo extends GBDeviceEvent {
private static final Logger LOG = LoggerFactory.getLogger(GBDeviceEventBatteryInfo.class);
public GregorianCalendar lastChargeTime = null;
public BatteryState state = BatteryState.UNKNOWN;
public int batteryIndex = 0;
@@ -31,9 +51,91 @@ public class GBDeviceEventBatteryInfo extends GBDeviceEvent {
public float voltage = -1f;
public boolean extendedInfoAvailable() {
if (numCharges != -1 && lastChargeTime != null) {
return true;
return numCharges != -1 && lastChargeTime != null;
}
@Override
public void evaluate(final Context context, final GBDevice device) {
LOG.info("Got BATTERY_INFO device event");
device.setBatteryLevel(this.level, this.batteryIndex);
device.setBatteryState(this.state, this.batteryIndex);
device.setBatteryVoltage(this.voltage, this.batteryIndex);
final DevicePrefs devicePrefs = GBApplication.getDevicePrefs(device);
final BatteryConfig batteryConfig = device.getDeviceCoordinator().getBatteryConfig(device)[this.batteryIndex];
if (this.level == GBDevice.BATTERY_UNKNOWN) {
// no level available, just "high" or "low"
GB.removeBatteryFullNotification(context);
if (devicePrefs.getBatteryNotifyLowEnabled(batteryConfig) && BatteryState.BATTERY_LOW.equals(this.state)) {
GB.updateBatteryLowNotification(context.getString(R.string.notif_battery_low, device.getAliasOrName()),
this.extendedInfoAvailable() ?
context.getString(R.string.notif_battery_low_extended, device.getAliasOrName(),
context.getString(R.string.notif_battery_low_bigtext_last_charge_time, DateFormat.getDateTimeInstance().format(this.lastChargeTime.getTime())) +
context.getString(R.string.notif_battery_low_bigtext_number_of_charges, String.valueOf(this.numCharges)))
: ""
, context);
} else if (devicePrefs.getBatteryNotifyFullEnabled(batteryConfig) && BatteryState.BATTERY_CHARGING_FULL.equals(this.state)) {
GB.removeBatteryLowNotification(context);
GB.updateBatteryFullNotification(context.getString(R.string.notif_battery_full, device.getAliasOrName()), "", context);
} else {
GB.removeBatteryLowNotification(context);
GB.removeBatteryFullNotification(context);
}
} else {
new StoreDataTask("Storing battery data", context, device, this).execute();
final boolean batteryNotifyLowEnabled = devicePrefs.getBatteryNotifyLowEnabled(batteryConfig);
final boolean isBatteryLow = this.level <= devicePrefs.getBatteryNotifyLowThreshold(batteryConfig) &&
(BatteryState.BATTERY_LOW.equals(this.state) || BatteryState.BATTERY_NORMAL.equals(this.state));
final boolean batteryNotifyFullEnabled = devicePrefs.getBatteryNotifyFullEnabled(batteryConfig);
final boolean isBatteryFull = this.level >= devicePrefs.getBatteryNotifyFullThreshold(batteryConfig) &&
(BatteryState.BATTERY_CHARGING.equals(this.state) || BatteryState.BATTERY_CHARGING_FULL.equals(this.state));
//show the notification if the battery level is below threshold and only if not connected to charger
if (batteryNotifyLowEnabled && isBatteryLow) {
GB.removeBatteryFullNotification(context);
GB.updateBatteryLowNotification(context.getString(R.string.notif_battery_low_percent, device.getAliasOrName(), String.valueOf(this.level)),
this.extendedInfoAvailable() ?
context.getString(R.string.notif_battery_low_percent, device.getAliasOrName(), String.valueOf(this.level)) + "\n" +
context.getString(R.string.notif_battery_low_bigtext_last_charge_time, DateFormat.getDateTimeInstance().format(this.lastChargeTime.getTime())) +
context.getString(R.string.notif_battery_low_bigtext_number_of_charges, String.valueOf(this.numCharges))
: ""
, context);
} else if (batteryNotifyFullEnabled && isBatteryFull) {
GB.removeBatteryLowNotification(context);
GB.updateBatteryFullNotification(context.getString(R.string.notif_battery_full, device.getAliasOrName()), "", context);
} else {
GB.removeBatteryLowNotification(context);
GB.removeBatteryFullNotification(context);
}
}
device.sendDeviceUpdateIntent(context);
}
public static class StoreDataTask extends DBAccess {
GBDeviceEventBatteryInfo deviceEvent;
GBDevice gbDevice;
public StoreDataTask(String task, Context context, GBDevice device, GBDeviceEventBatteryInfo deviceEvent) {
super(task, context);
this.deviceEvent = deviceEvent;
this.gbDevice = device;
}
@Override
protected void doInBackground(DBHandler handler) {
DaoSession daoSession = handler.getDaoSession();
Device device = DBHelper.getDevice(gbDevice, daoSession);
int ts = (int) (System.currentTimeMillis() / 1000);
BatteryLevel batteryLevel = new BatteryLevel();
batteryLevel.setTimestamp(ts);
batteryLevel.setBatteryIndex(deviceEvent.batteryIndex);
batteryLevel.setDevice(device);
batteryLevel.setLevel(deviceEvent.level);
handler.getDaoSession().getBatteryLevelDao().insert(batteryLevel);
}
return false;
}
}
@@ -17,7 +17,18 @@
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import android.content.Intent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBCallControlReceiver;
public class GBDeviceEventCallControl extends GBDeviceEvent {
private static final Logger LOG = LoggerFactory.getLogger(GBDeviceEventCallControl.class);
public Event event = Event.UNKNOWN;
public GBDeviceEventCallControl() {
@@ -27,6 +38,22 @@ public class GBDeviceEventCallControl extends GBDeviceEvent {
this.event = event;
}
@Override
public void evaluate(final Context context, final GBDevice device) {
LOG.info("Got event for CALL_CONTROL");
if (event == GBDeviceEventCallControl.Event.IGNORE) {
LOG.info("Sending intent for mute");
final Intent broadcastIntent = new Intent(context.getPackageName() + ".MUTE_CALL");
broadcastIntent.setPackage(context.getPackageName());
context.sendBroadcast(broadcastIntent);
return;
}
final Intent callIntent = new Intent(GBCallControlReceiver.ACTION_CALLCONTROL);
callIntent.putExtra("event", event.ordinal());
callIntent.setPackage(context.getPackageName());
context.sendBroadcast(callIntent);
}
public enum Event {
UNKNOWN,
ACCEPT,
@@ -16,9 +16,23 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import android.content.Intent;
import nodomain.freeyourgadget.gadgetbridge.activities.CameraActivity;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class GBDeviceEventCameraRemote extends GBDeviceEvent {
public Event event = Event.UNKNOWN;
@Override
public void evaluate(final Context context, final GBDevice device) {
final Intent cameraIntent = new Intent(context, CameraActivity.class);
cameraIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
cameraIntent.putExtra(CameraActivity.intentExtraEvent, GBDeviceEventCameraRemote.eventToInt(event));
context.startActivity(cameraIntent);
}
public enum Event {
UNKNOWN,
OPEN_CAMERA,
@@ -16,7 +16,15 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
public class GBDeviceEventDisplayMessage {
import android.content.Context;
import android.content.Intent;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class GBDeviceEventDisplayMessage extends GBDeviceEvent {
public String message;
public int duration;
public int severity;
@@ -35,4 +43,16 @@ public class GBDeviceEventDisplayMessage {
this.duration = duration;
this.severity = severity;
}
@Override
public void evaluate(final Context context, final GBDevice device) {
GB.log(this.message, this.severity, null);
Intent messageIntent = new Intent(GB.ACTION_DISPLAY_MESSAGE);
messageIntent.putExtra(GB.DISPLAY_MESSAGE_MESSAGE, this.message);
messageIntent.putExtra(GB.DISPLAY_MESSAGE_DURATION, this.duration);
messageIntent.putExtra(GB.DISPLAY_MESSAGE_SEVERITY, this.severity);
LocalBroadcastManager.getInstance(context).sendBroadcast(messageIntent);
}
}
@@ -17,9 +17,119 @@
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID;
import android.app.PendingIntent;
import android.companion.CompanionDeviceManager;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.FindPhoneActivity;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
public class GBDeviceEventFindPhone extends GBDeviceEvent {
private static final Logger LOG = LoggerFactory.getLogger(GBDeviceEventFindPhone.class);
public Event event = Event.UNKNOWN;
@Override
public void evaluate(final Context context, final GBDevice device) {
LOG.info("Got GBDeviceEventFindPhone: {}", this.event);
switch (this.event) {
case START:
handleGBDeviceEventFindPhoneStart(context, true);
break;
case START_VIBRATE:
handleGBDeviceEventFindPhoneStart(context, false);
break;
case VIBRATE:
final Intent intentVibrate = new Intent(FindPhoneActivity.ACTION_VIBRATE);
intentVibrate.setPackage(BuildConfig.APPLICATION_ID);
LocalBroadcastManager.getInstance(context).sendBroadcast(intentVibrate);
break;
case RING:
final Intent intentRing = new Intent(FindPhoneActivity.ACTION_RING);
intentRing.setPackage(BuildConfig.APPLICATION_ID);
LocalBroadcastManager.getInstance(context).sendBroadcast(intentRing);
break;
case STOP:
final Intent intentStop = new Intent(FindPhoneActivity.ACTION_FOUND);
intentStop.setPackage(BuildConfig.APPLICATION_ID);
LocalBroadcastManager.getInstance(context).sendBroadcast(intentStop);
break;
default:
LOG.warn("unknown GBDeviceEventFindPhone");
}
}
private void handleGBDeviceEventFindPhoneStart(final Context context, final boolean ring) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { // this could be used if app in foreground // TODO: Below Q?
final Intent startIntent = new Intent(context, FindPhoneActivity.class);
startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startIntent.putExtra(FindPhoneActivity.EXTRA_RING, ring);
context.startActivity(startIntent);
} else {
handleGBDeviceEventFindPhoneStartNotification(context, ring);
}
}
@RequiresApi(Build.VERSION_CODES.Q)
private void handleGBDeviceEventFindPhoneStartNotification(final Context context, final boolean ring) {
LOG.info("Got handleGBDeviceEventFindPhoneStartNotification");
final CompanionDeviceManager manager = (CompanionDeviceManager) context.getSystemService(Context.COMPANION_DEVICE_SERVICE);
if (manager.getAssociations().isEmpty()) {
// On Android Q and above, we need the device to be paired as companion. If it is not, display a notification
// notifying the user and linking to further instructions
final Intent instructionsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://gadgetbridge.org/basics/pairing/companion-device/"));
final PendingIntent pi = PendingIntentUtils.getActivity(context, 0, instructionsIntent, PendingIntent.FLAG_ONE_SHOT, false);
final NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID)
.setSmallIcon(R.drawable.ic_warning)
.setOngoing(false)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pi)
.setAutoCancel(true)
.setContentTitle(context.getString(R.string.find_my_phone_notification))
.setContentText(context.getString(R.string.find_my_phone_companion_warning));
GB.notify(GB.NOTIFICATION_ID_PHONE_FIND, notification.build(), context);
return;
}
final Intent intent = new Intent(context, FindPhoneActivity.class);
intent.setPackage(BuildConfig.APPLICATION_ID);
intent.putExtra(FindPhoneActivity.EXTRA_RING, ring);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final PendingIntent pi = PendingIntentUtils.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, false);
final NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID)
.setSmallIcon(R.drawable.ic_notification)
.setOngoing(false)
.setFullScreenIntent(pi, true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.setGroup("BackgroundService")
.setContentTitle(context.getString(R.string.find_my_phone_notification));
GB.notify(GB.NOTIFICATION_ID_PHONE_FIND, notification.build(), context);
context.startActivity(intent);
LOG.debug("CompanionDeviceManager associations were found, starting intent");
}
public enum Event {
UNKNOWN,
START,
@@ -1,4 +1,4 @@
/* Copyright (C) 2018-2024 José Rebelo
/* Copyright (C) 2018-2025 José Rebelo
This file is part of Gadgetbridge.
@@ -16,10 +16,28 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import androidx.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class GBDeviceEventFmFrequency extends GBDeviceEvent {
public final float frequency;
public GBDeviceEventFmFrequency(final float frequency) {
this.frequency = frequency;
}
@NonNull
@Override
public String toString() {
return super.toString() + "frequency: " + frequency;
}
@Override
public void evaluate(final Context context, final GBDevice device) {
device.setExtraInfo("fm_frequency", frequency);
device.sendDeviceUpdateIntent(context);
}
}
@@ -16,10 +16,30 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import androidx.annotation.NonNull;
import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class GBDeviceEventLEDColor extends GBDeviceEvent {
public final int color;
public GBDeviceEventLEDColor(final int color) {
this.color = color;
}
@NonNull
@Override
public String toString() {
return super.toString() + "color: " + Integer.toHexString(this.color).toUpperCase(Locale.ROOT);
}
@Override
public void evaluate(final Context context, final GBDevice device) {
device.setExtraInfo("led_color", this.color);
device.sendDeviceUpdateIntent(context);
}
}
@@ -17,9 +17,29 @@
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import android.content.Intent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBMusicControlReceiver;
public class GBDeviceEventMusicControl extends GBDeviceEvent {
private static final Logger LOG = LoggerFactory.getLogger(GBDeviceEventMusicControl.class);
public Event event = Event.UNKNOWN;
@Override
public void evaluate(final Context context, final GBDevice device) {
LOG.info("Got event for MUSIC_CONTROL");
final Intent musicIntent = new Intent(GBMusicControlReceiver.ACTION_MUSICCONTROL);
musicIntent.putExtra("event", event.ordinal());
musicIntent.setPackage(context.getPackageName());
context.sendBroadcast(musicIntent);
}
public enum Event {
UNKNOWN,
PLAY,
@@ -16,13 +16,81 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.telephony.SmsManager;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class GBDeviceEventNotificationControl extends GBDeviceEvent {
private static final Logger LOG = LoggerFactory.getLogger(GBDeviceEventNotificationControl.class);
public long handle;
public String phoneNumber;
public String reply;
public String title;
public Event event = Event.UNKNOWN;
@Override
public void evaluate(final Context context, final GBDevice device) {
LOG.info("Got NOTIFICATION CONTROL device event");
final String action;
switch (event) {
case DISMISS:
action = NotificationListener.ACTION_DISMISS;
break;
case DISMISS_ALL:
action = NotificationListener.ACTION_DISMISS_ALL;
break;
case OPEN:
action = NotificationListener.ACTION_OPEN;
break;
case MUTE:
action = NotificationListener.ACTION_MUTE;
break;
case REPLY:
if (phoneNumber == null) {
phoneNumber = GBApplication.getIDSenderLookup().lookup((int) (handle >> 4));
}
if (phoneNumber != null) {
LOG.info("Got notification reply for SMS from {} : {}", phoneNumber, reply);
SmsManager.getDefault().sendTextMessage(phoneNumber, null, reply, null, null);
return;
} else {
LOG.info("Got notification reply for notification id {} : {}", handle, reply);
action = NotificationListener.ACTION_REPLY;
}
break;
case UNKNOWN:
default:
LOG.error("Unknown notification control action {}", event);
return;
}
final Intent notificationListenerIntent = new Intent(action);
notificationListenerIntent.putExtra("handle", handle);
notificationListenerIntent.putExtra("title", title);
if (reply != null) {
final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(device.getAddress());
String suffix = prefs.getString("canned_reply_suffix", null);
if (suffix != null && !Objects.equals(suffix, "")) {
reply += suffix;
}
notificationListenerIntent.putExtra("reply", reply);
}
LocalBroadcastManager.getInstance(context).sendBroadcast(notificationListenerIntent);
}
public enum Event {
UNKNOWN,
DISMISS,
@@ -16,7 +16,38 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import androidx.core.app.NotificationCompat;
import androidx.core.content.FileProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
public class GBDeviceEventScreenshot extends GBDeviceEvent {
private static final Logger LOG = LoggerFactory.getLogger(GBDeviceEventScreenshot.class);
private static final int NOTIFICATION_ID_SCREENSHOT = 8000;
private final byte[] data;
public GBDeviceEventScreenshot(final byte[] data) {
@@ -26,4 +57,52 @@ public class GBDeviceEventScreenshot extends GBDeviceEvent {
public byte[] getData() {
return data;
}
@Override
public void evaluate(final Context context, final GBDevice device) {
if (getData() == null) {
LOG.warn("Screenshot data is null");
return;
}
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-hhmmss", Locale.US);
String filename = "screenshot_" + dateFormat.format(new Date()) + ".bmp";
try {
final String fullpath = GB.writeScreenshot(this, filename);
final Bitmap bmp = BitmapFactory.decodeFile(fullpath);
final Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
final Uri screenshotURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".screenshot_provider", new File(fullpath));
intent.setDataAndType(screenshotURI, "image/*");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
final PendingIntent pIntent = PendingIntentUtils.getActivity(context, 0, intent, 0, false);
final Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("image/*");
shareIntent.putExtra(Intent.EXTRA_STREAM, screenshotURI);
final PendingIntent pendingShareIntent = PendingIntentUtils.getActivity(context, 0, Intent.createChooser(shareIntent, context.getString(R.string.share_screenshot)),
PendingIntent.FLAG_UPDATE_CURRENT, false);
final NotificationCompat.Action action = new NotificationCompat.Action.Builder(android.R.drawable.ic_menu_share, context.getString(R.string.share), pendingShareIntent).build();
final Notification notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID)
.setContentTitle(context.getString(R.string.screenshot_taken))
.setTicker(context.getString(R.string.screenshot_taken))
.setContentText(filename)
.setSmallIcon(R.drawable.ic_notification)
.setStyle(new NotificationCompat.BigPictureStyle()
.bigPicture(bmp))
.setContentIntent(pIntent)
.addAction(action)
.setAutoCancel(true)
.build();
GB.notify(NOTIFICATION_ID_SCREENSHOT, notification, context);
} catch (IOException ex) {
LOG.error("Error writing screenshot", ex);
}
}
}
@@ -16,6 +16,10 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class GBDeviceEventSendBytes extends GBDeviceEvent {
public byte[] encodedBytes;
@@ -25,4 +29,9 @@ public class GBDeviceEventSendBytes extends GBDeviceEvent {
public GBDeviceEventSendBytes(final byte[] encodedBytes) {
this.encodedBytes = encodedBytes;
}
@Override
public void evaluate(final Context context, final GBDevice device) {
// Handled in support class
}
}
@@ -1,4 +1,4 @@
/* Copyright (C) 2023-2024 José Rebelo
/* Copyright (C) 2023-2025 José Rebelo
This file is part of Gadgetbridge.
@@ -16,6 +16,13 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import androidx.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.SilentMode;
public class GBDeviceEventSilentMode extends GBDeviceEvent {
private final boolean enabled;
@@ -26,4 +33,15 @@ public class GBDeviceEventSilentMode extends GBDeviceEvent {
public boolean isEnabled() {
return enabled;
}
@NonNull
@Override
public String toString() {
return super.toString() + "enabled: " + enabled;
}
@Override
public void evaluate(final Context context, final GBDevice device) {
SilentMode.setPhoneSilentMode(device.getAddress(), isEnabled());
}
}
@@ -16,15 +16,66 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import java.util.Locale;
import android.content.Context;
import androidx.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.SleepState;
public class GBDeviceEventSleepStateDetection extends GBDeviceEvent {
private static final Logger LOG = LoggerFactory.getLogger(GBDeviceEventSleepStateDetection.class);
public SleepState sleepState;
@NonNull
@Override
public String toString() {
return super.toString() + String.format(Locale.ROOT, "sleepState=%s", sleepState);
}
@Override
public void evaluate(final Context context, final GBDevice device) {
if (this.sleepState == SleepState.UNKNOWN) {
return;
}
final String actionPreferenceKey, messagePreferenceKey;
final int defaultBroadcastMessageResource;
switch (this.sleepState) {
case AWAKE:
actionPreferenceKey = DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_WOKE_UP_SELECTIONS;
messagePreferenceKey = DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_WOKE_UP_BROADCAST;
defaultBroadcastMessageResource = R.string.prefs_events_forwarding_wokeup_broadcast_default_value;
break;
case ASLEEP:
actionPreferenceKey = DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_FELL_SLEEP_SELECTIONS;
messagePreferenceKey = DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_FELL_SLEEP_BROADCAST;
defaultBroadcastMessageResource = R.string.prefs_events_forwarding_fellsleep_broadcast_default_value;
break;
default:
LOG.warn("Unable to deduce action and broadcast message preference key for sleep state {}", this.sleepState);
return;
}
final Set<String> actions = GBApplication.getDevicePrefs(device).getStringSet(actionPreferenceKey, Collections.emptySet());
if (actions.isEmpty()) {
return;
}
String broadcastMessage = GBApplication.getDevicePrefs(device).getString(messagePreferenceKey, context.getString(defaultBroadcastMessageResource));
handleDeviceAction(context, device, actions, broadcastMessage);
}
}
@@ -16,6 +16,9 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
@@ -29,4 +32,10 @@ public class GBDeviceEventUpdateDeviceInfo extends GBDeviceEvent {
public GBDeviceEventUpdateDeviceInfo(final String name, final String details) {
this(new GenericItem(name, details));
}
@Override
public void evaluate(final Context context, final GBDevice device) {
device.addDeviceInfo(this.item);
device.sendDeviceUpdateIntent(context);
}
}
@@ -16,6 +16,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class GBDeviceEventUpdateDeviceState extends GBDeviceEvent {
@@ -24,4 +26,10 @@ public class GBDeviceEventUpdateDeviceState extends GBDeviceEvent {
public GBDeviceEventUpdateDeviceState(final GBDevice.State state) {
this.state = state;
}
@Override
public void evaluate(final Context context, final GBDevice device) {
device.setState(state);
device.sendDeviceUpdateIntent(context, GBDevice.DeviceUpdateSubject.DEVICE_STATE);
}
}
@@ -16,6 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import android.content.SharedPreferences;
import org.slf4j.Logger;
@@ -25,6 +26,9 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class GBDeviceEventUpdatePreferences extends GBDeviceEvent {
private static final Logger LOG = LoggerFactory.getLogger(GBDeviceEventUpdatePreferences.class);
@@ -55,12 +59,18 @@ public class GBDeviceEventUpdatePreferences extends GBDeviceEvent {
return this;
}
@Override
public void evaluate(final Context context, final GBDevice device) {
update(GBApplication.getDeviceSpecificSharedPrefs(device.getAddress()));
device.sendDeviceUpdateIntent(context);
}
/**
* Update a {@link SharedPreferences} instance with the preferences in the event.
*
* @param prefs the SharedPreferences object to update.
*/
public void update(final SharedPreferences prefs) {
private void update(final SharedPreferences prefs) {
final SharedPreferences.Editor editor = prefs.edit();
for (String key : preferences.keySet()) {
@@ -16,10 +16,20 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import androidx.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class GBDeviceEventVersionInfo extends GBDeviceEvent {
private static final Logger LOG = LoggerFactory.getLogger(GBDeviceEventVersionInfo.class);
public String fwVersion = "N/A";
public String fwVersion2 = null;
public String hwVersion = "N/A";
@@ -32,8 +42,25 @@ public class GBDeviceEventVersionInfo extends GBDeviceEvent {
}
}
@NonNull
@Override
public String toString() {
return super.toString() + "fwVersion: " + fwVersion + "; fwVersion2: " + fwVersion2 + "; hwVersion: " + hwVersion;
}
@Override
public void evaluate(final Context context, final GBDevice device) {
LOG.info("Got event for VERSION_INFO: {}", this);
if (device == null) {
return;
}
if (fwVersion != null) {
device.setFirmwareVersion(fwVersion);
}
if (fwVersion2 != null) {
device.setFirmwareVersion2(fwVersion2);
}
device.setModel(hwVersion);
device.sendDeviceUpdateIntent(context);
}
}
@@ -16,15 +16,60 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import java.util.Locale;
import android.content.Context;
import androidx.annotation.NonNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.WearingState;
public class GBDeviceEventWearState extends GBDeviceEvent {
private static final Logger LOG = LoggerFactory.getLogger(GBDeviceEventWearState.class);
public WearingState wearingState = WearingState.UNKNOWN;
@NonNull
@Override
public String toString() {
return super.toString() + String.format(Locale.ROOT, "wearingState=%s", wearingState);
}
@Override
public void evaluate(final Context context, final GBDevice device) {
if (this.wearingState == WearingState.UNKNOWN) {
LOG.warn("WEAR_STATE state is UNKNOWN, aborting further evaluation");
return;
}
if (this.wearingState != WearingState.NOT_WEARING) {
LOG.debug("WEAR_STATE state is not NOT_WEARING, aborting further evaluation");
}
Set<String> actionOnUnwear = GBApplication.getDevicePrefs(device).getStringSet(
DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_START_NON_WEAR_SELECTIONS,
Collections.emptySet()
);
// check if an action is set
if (actionOnUnwear.isEmpty()) {
return;
}
String broadcastMessage = GBApplication.getDevicePrefs(device).getString(
DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_START_NON_WEAR_BROADCAST,
context.getString(R.string.prefs_events_forwarding_startnonwear_broadcast_default_value)
);
handleDeviceAction(context, device, actionOnUnwear, broadcastMessage);
}
}
@@ -16,8 +16,17 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.activities.musicmanager.MusicManagerActivity;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusicPlaylist;
@@ -28,4 +37,34 @@ public class GBDeviceMusicData extends GBDeviceEvent {
public String deviceInfo = null;
public int maxMusicCount = 0;
public int maxPlaylistCount = 0;
@Override
public void evaluate(final Context context, final GBDevice device) {
final Intent intent = new Intent(MusicManagerActivity.ACTION_MUSIC_DATA);
intent.putExtra("type", this.type);
if (this.list != null) {
final ArrayList<GBDeviceMusic> list = new ArrayList<>(this.list);
intent.putExtra("musicList", list);
}
if (this.playlists != null) {
final ArrayList<GBDeviceMusicPlaylist> list = new ArrayList<>(this.playlists);
intent.putExtra("musicPlaylist", list);
}
if (!TextUtils.isEmpty(this.deviceInfo)) {
intent.putExtra("deviceInfo", this.deviceInfo);
}
if (this.maxMusicCount > 0) {
intent.putExtra("maxMusicCount", this.maxMusicCount);
}
if (this.maxPlaylistCount > 0) {
intent.putExtra("maxPlaylistCount", this.maxPlaylistCount);
}
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
}
@@ -16,12 +16,33 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents;
import android.content.Context;
import android.content.Intent;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import java.util.ArrayList;
import nodomain.freeyourgadget.gadgetbridge.activities.musicmanager.MusicManagerActivity;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class GBDeviceMusicUpdate extends GBDeviceEvent {
public boolean success = false;
public int operation = -1;
public int playlistIndex = -1;
public String playlistName;
public ArrayList<Integer> musicIds = null;
@Override
public void evaluate(final Context context, final GBDevice device) {
final Intent intent = new Intent(MusicManagerActivity.ACTION_MUSIC_UPDATE);
intent.putExtra("success", success);
intent.putExtra("operation", operation);
intent.putExtra("playlistIndex", playlistIndex);
intent.putExtra("playlistName", playlistName);
intent.putExtra("musicIds", musicIds);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
}
@@ -16,9 +16,12 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.deviceevents.pebble;
import android.content.Context;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class GBDeviceEventDataLogging extends GBDeviceEvent {
public static final int COMMAND_RECEIVE_DATA = 1;
@@ -30,4 +33,9 @@ public class GBDeviceEventDataLogging extends GBDeviceEvent {
public long tag;
public byte pebbleDataType;
public Object[] data;
@Override
public void evaluate(final Context context, final GBDevice device) {
// FIXME: Pebble-specific, handled in support class
}
}
@@ -19,88 +19,26 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.companion.CompanionDeviceManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.Location;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.text.TextUtils;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.core.content.FileProvider;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.CameraActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.FindPhoneActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AbstractAppManagerFragment;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.activities.musicmanager.MusicManagerActivity;
import nodomain.freeyourgadget.gadgetbridge.capabilities.loyaltycards.LoyaltyCard;
import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFmFrequency;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSleepStateDetection;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSilentMode;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventLEDColor;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceState;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventWearState;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceMusicData;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceMusicUpdate;
import nodomain.freeyourgadget.gadgetbridge.entities.BatteryLevel;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.externalevents.CalendarReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusic;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceMusicPlaylist;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
@@ -109,20 +47,11 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.SleepState;
import nodomain.freeyourgadget.gadgetbridge.model.WearingState;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.model.WorldClock;
import nodomain.freeyourgadget.gadgetbridge.model.NavigationInfoSpec;
import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBCallControlReceiver;
import nodomain.freeyourgadget.gadgetbridge.service.receivers.GBMusicControlReceiver;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.PendingIntentUtils;
import nodomain.freeyourgadget.gadgetbridge.util.SilentMode;
import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID;
// TODO: support option for a single reminder notification when notifications could not be delivered?
// conditions: app was running and received notifications, but device was not connected.
// maybe need to check for "unread notifications" on device for that.
@@ -133,7 +62,6 @@ import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_
*/
public abstract class AbstractDeviceSupport implements DeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(AbstractDeviceSupport.class);
private static final int NOTIFICATION_ID_SCREENSHOT = 8000;
protected GBDevice gbDevice;
private BluetoothAdapter btAdapter;
@@ -207,643 +135,14 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
return context;
}
public void evaluateGBDeviceEvent(GBDeviceEvent deviceEvent) {
if (deviceEvent instanceof GBDeviceEventMusicControl) {
handleGBDeviceEvent((GBDeviceEventMusicControl) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventCallControl) {
handleGBDeviceEvent((GBDeviceEventCallControl) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventCameraRemote) {
handleGBDeviceEvent((GBDeviceEventCameraRemote) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventVersionInfo) {
handleGBDeviceEvent((GBDeviceEventVersionInfo) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventAppInfo) {
handleGBDeviceEvent((GBDeviceEventAppInfo) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventScreenshot) {
handleGBDeviceEvent((GBDeviceEventScreenshot) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventNotificationControl) {
handleGBDeviceEvent((GBDeviceEventNotificationControl) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventBatteryInfo) {
handleGBDeviceEvent((GBDeviceEventBatteryInfo) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventFindPhone) {
handleGBDeviceEvent((GBDeviceEventFindPhone) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventLEDColor) {
handleGBDeviceEvent((GBDeviceEventLEDColor) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventUpdateDeviceInfo) {
handleGBDeviceEvent((GBDeviceEventUpdateDeviceInfo) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventUpdatePreferences) {
handleGBDeviceEvent((GBDeviceEventUpdatePreferences) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventUpdateDeviceState) {
handleGBDeviceEvent((GBDeviceEventUpdateDeviceState) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventSilentMode) {
handleGBDeviceEvent((GBDeviceEventSilentMode) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventFmFrequency) {
handleGBDeviceEvent((GBDeviceEventFmFrequency) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventWearState) {
handleGBDeviceEvent((GBDeviceEventWearState) deviceEvent);
} else if (deviceEvent instanceof GBDeviceEventSleepStateDetection) {
handleGBDeviceEvent((GBDeviceEventSleepStateDetection) deviceEvent);
} else if (deviceEvent instanceof GBDeviceMusicData) {
handleGBDeviceEvent((GBDeviceMusicData) deviceEvent);
} else if (deviceEvent instanceof GBDeviceMusicUpdate) {
handleGBDeviceEvent((GBDeviceMusicUpdate) deviceEvent);
}
public void evaluateGBDeviceEvent(final GBDeviceEvent deviceEvent) {
LOG.debug("Evaluating event: {}", deviceEvent);
deviceEvent.evaluate(context, gbDevice);
}
private void handleGBDeviceEvent(GBDeviceEventSilentMode deviceEvent) {
LOG.info("Got GBDeviceEventSilentMode: enabled = {}", deviceEvent.isEnabled());
SilentMode.setPhoneSilentMode(getDevice().getAddress(), deviceEvent.isEnabled());
}
private void handleGBDeviceEvent(final GBDeviceEventFindPhone deviceEvent) {
final Context context = getContext();
LOG.info("Got GBDeviceEventFindPhone: {}", deviceEvent.event);
switch (deviceEvent.event) {
case START:
handleGBDeviceEventFindPhoneStart(true);
break;
case START_VIBRATE:
handleGBDeviceEventFindPhoneStart(false);
break;
case VIBRATE:
final Intent intentVibrate = new Intent(FindPhoneActivity.ACTION_VIBRATE);
intentVibrate.setPackage(BuildConfig.APPLICATION_ID);
LocalBroadcastManager.getInstance(context).sendBroadcast(intentVibrate);
break;
case RING:
final Intent intentRing = new Intent(FindPhoneActivity.ACTION_RING);
intentRing.setPackage(BuildConfig.APPLICATION_ID);
LocalBroadcastManager.getInstance(context).sendBroadcast(intentRing);
break;
case STOP:
final Intent intentStop = new Intent(FindPhoneActivity.ACTION_FOUND);
intentStop.setPackage(BuildConfig.APPLICATION_ID);
LocalBroadcastManager.getInstance(context).sendBroadcast(intentStop);
break;
default:
LOG.warn("unknown GBDeviceEventFindPhone");
}
}
private void handleGBDeviceEventFindPhoneStart(final boolean ring) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { // this could be used if app in foreground // TODO: Below Q?
Intent startIntent = new Intent(getContext(), FindPhoneActivity.class);
startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startIntent.putExtra(FindPhoneActivity.EXTRA_RING, ring);
context.startActivity(startIntent);
} else {
handleGBDeviceEventFindPhoneStartNotification(ring);
}
}
@RequiresApi(Build.VERSION_CODES.Q)
private void handleGBDeviceEventFindPhoneStartNotification(final boolean ring) {
LOG.info("Got handleGBDeviceEventFindPhoneStartNotification");
final CompanionDeviceManager manager = (CompanionDeviceManager) context.getSystemService(Context.COMPANION_DEVICE_SERVICE);
if (manager.getAssociations().isEmpty()) {
// On Android Q and above, we need the device to be paired as companion. If it is not, display a notification
// notifying the user and linking to further instructions
final Intent instructionsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://gadgetbridge.org/basics/pairing/companion-device/"));
final PendingIntent pi = PendingIntentUtils.getActivity(context, 0, instructionsIntent, PendingIntent.FLAG_ONE_SHOT, false);
final NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID)
.setSmallIcon(R.drawable.ic_warning)
.setOngoing(false)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pi)
.setAutoCancel(true)
.setContentTitle(context.getString(R.string.find_my_phone_notification))
.setContentText(context.getString(R.string.find_my_phone_companion_warning));
GB.notify(GB.NOTIFICATION_ID_PHONE_FIND, notification.build(), context);
return;
}
final Intent intent = new Intent(context, FindPhoneActivity.class);
intent.setPackage(BuildConfig.APPLICATION_ID);
intent.putExtra(FindPhoneActivity.EXTRA_RING, ring);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final PendingIntent pi = PendingIntentUtils.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, false);
final NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID)
.setSmallIcon(R.drawable.ic_notification)
.setOngoing(false)
.setFullScreenIntent(pi, true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setAutoCancel(true)
.setGroup("BackgroundService")
.setContentTitle(context.getString(R.string.find_my_phone_notification));
GB.notify(GB.NOTIFICATION_ID_PHONE_FIND, notification.build(), context);
context.startActivity(intent);
LOG.debug("CompanionDeviceManager associations were found, starting intent");
}
private void handleGBDeviceEvent(GBDeviceEventMusicControl musicEvent) {
Context context = getContext();
LOG.info("Got event for MUSIC_CONTROL");
Intent musicIntent = new Intent(GBMusicControlReceiver.ACTION_MUSICCONTROL);
musicIntent.putExtra("event", musicEvent.event.ordinal());
musicIntent.setPackage(context.getPackageName());
context.sendBroadcast(musicIntent);
}
private void handleGBDeviceEvent(GBDeviceEventCallControl callEvent) {
Context context = getContext();
LOG.info("Got event for CALL_CONTROL");
if(callEvent.event == GBDeviceEventCallControl.Event.IGNORE) {
LOG.info("Sending intent for mute");
Intent broadcastIntent = new Intent(context.getPackageName() + ".MUTE_CALL");
broadcastIntent.setPackage(context.getPackageName());
context.sendBroadcast(broadcastIntent);
return;
}
Intent callIntent = new Intent(GBCallControlReceiver.ACTION_CALLCONTROL);
callIntent.putExtra("event", callEvent.event.ordinal());
callIntent.setPackage(context.getPackageName());
context.sendBroadcast(callIntent);
}
protected void handleGBDeviceEvent(GBDeviceEventCameraRemote cameraRemoteEvent) {
Intent cameraIntent = new Intent(getContext(), CameraActivity.class);
cameraIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
cameraIntent.putExtra(CameraActivity.intentExtraEvent, GBDeviceEventCameraRemote.eventToInt(cameraRemoteEvent.event));
getContext().startActivity(cameraIntent);
}
protected void handleGBDeviceEvent(GBDeviceEventVersionInfo infoEvent) {
Context context = getContext();
LOG.info("Got event for VERSION_INFO: " + infoEvent);
if (gbDevice == null) {
return;
}
if (infoEvent.fwVersion != null) {
gbDevice.setFirmwareVersion(infoEvent.fwVersion);
}
if (infoEvent.fwVersion2 != null) {
gbDevice.setFirmwareVersion2(infoEvent.fwVersion2);
}
gbDevice.setModel(infoEvent.hwVersion);
gbDevice.sendDeviceUpdateIntent(context);
}
protected void handleGBDeviceEvent(GBDeviceEventLEDColor colorEvent) {
Context context = getContext();
LOG.info("Got event for LED Color: #" + Integer.toHexString(colorEvent.color).toUpperCase(Locale.ROOT));
if (gbDevice == null) {
return;
}
gbDevice.setExtraInfo("led_color", colorEvent.color);
gbDevice.sendDeviceUpdateIntent(context);
}
protected void handleGBDeviceEvent(GBDeviceEventUpdateDeviceInfo itemEvent) {
if (gbDevice == null) {
return;
}
gbDevice.addDeviceInfo(itemEvent.item);
gbDevice.sendDeviceUpdateIntent(context);
}
protected void handleGBDeviceEvent(GBDeviceEventUpdatePreferences savePreferencesEvent) {
if (gbDevice == null) {
return;
}
savePreferencesEvent.update(GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()));
gbDevice.sendDeviceUpdateIntent(context);
}
protected void handleGBDeviceEvent(GBDeviceEventUpdateDeviceState updateDeviceState) {
if (gbDevice == null) {
return;
}
gbDevice.setState(updateDeviceState.state);
gbDevice.sendDeviceUpdateIntent(getContext(), GBDevice.DeviceUpdateSubject.DEVICE_STATE);
}
protected void handleGBDeviceEvent(GBDeviceEventFmFrequency frequencyEvent) {
Context context = getContext();
LOG.info("Got event for FM Frequency");
if (gbDevice == null) {
return;
}
gbDevice.setExtraInfo("fm_frequency", frequencyEvent.frequency);
gbDevice.sendDeviceUpdateIntent(context);
}
private void handleGBDeviceEvent(GBDeviceEventAppInfo appInfoEvent) {
Context context = getContext();
LOG.info("Got event for APP_INFO");
Intent appInfoIntent = new Intent(AbstractAppManagerFragment.ACTION_REFRESH_APPLIST);
int appCount = appInfoEvent.apps.length;
appInfoIntent.putExtra("app_count", appCount);
for (int i = 0; i < appCount; i++) {
appInfoIntent.putExtra("app_name" + i, appInfoEvent.apps[i].getName());
appInfoIntent.putExtra("app_creator" + i, appInfoEvent.apps[i].getCreator());
appInfoIntent.putExtra("app_version" + i, appInfoEvent.apps[i].getVersion());
appInfoIntent.putExtra("app_uuid" + i, appInfoEvent.apps[i].getUUID().toString());
appInfoIntent.putExtra("app_type" + i, appInfoEvent.apps[i].getType().ordinal());
}
LocalBroadcastManager.getInstance(context).sendBroadcast(appInfoIntent);
}
private void handleGBDeviceEvent(GBDeviceEventScreenshot screenshot) {
if (screenshot.getData() == null) {
LOG.warn("Screnshot data is null");
return;
}
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-hhmmss", Locale.US);
String filename = "screenshot_" + dateFormat.format(new Date()) + ".bmp";
try {
String fullpath = GB.writeScreenshot(screenshot, filename);
Bitmap bmp = BitmapFactory.decodeFile(fullpath);
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
Uri screenshotURI = FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".screenshot_provider", new File(fullpath));
intent.setDataAndType(screenshotURI, "image/*");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
PendingIntent pIntent = PendingIntentUtils.getActivity(context, 0, intent, 0, false);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("image/*");
shareIntent.putExtra(Intent.EXTRA_STREAM, screenshotURI);
PendingIntent pendingShareIntent = PendingIntentUtils.getActivity(context, 0, Intent.createChooser(shareIntent, context.getString(R.string.share_screenshot)),
PendingIntent.FLAG_UPDATE_CURRENT, false);
NotificationCompat.Action action = new NotificationCompat.Action.Builder(android.R.drawable.ic_menu_share, context.getString(R.string.share), pendingShareIntent).build();
Notification notif = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID)
.setContentTitle(context.getString(R.string.screenshot_taken))
.setTicker(context.getString(R.string.screenshot_taken))
.setContentText(filename)
.setSmallIcon(R.drawable.ic_notification)
.setStyle(new NotificationCompat.BigPictureStyle()
.bigPicture(bmp))
.setContentIntent(pIntent)
.addAction(action)
.setAutoCancel(true)
.build();
GB.notify(NOTIFICATION_ID_SCREENSHOT, notif, context);
} catch (IOException ex) {
LOG.error("Error writing screenshot", ex);
}
}
private void handleGBDeviceEvent(GBDeviceEventNotificationControl deviceEvent) {
Context context = getContext();
LOG.info("Got NOTIFICATION CONTROL device event");
String action = null;
switch (deviceEvent.event) {
case DISMISS:
action = NotificationListener.ACTION_DISMISS;
break;
case DISMISS_ALL:
action = NotificationListener.ACTION_DISMISS_ALL;
break;
case OPEN:
action = NotificationListener.ACTION_OPEN;
break;
case MUTE:
action = NotificationListener.ACTION_MUTE;
break;
case REPLY:
if (deviceEvent.phoneNumber == null) {
deviceEvent.phoneNumber = GBApplication.getIDSenderLookup().lookup((int) (deviceEvent.handle >> 4));
}
if (deviceEvent.phoneNumber != null) {
LOG.info("Got notification reply for SMS from {} : {}", deviceEvent.phoneNumber, deviceEvent.reply);
SmsManager.getDefault().sendTextMessage(deviceEvent.phoneNumber, null, deviceEvent.reply, null, null);
} else {
LOG.info("Got notification reply for notification id {} : {}", deviceEvent.handle, deviceEvent.reply);
action = NotificationListener.ACTION_REPLY;
}
break;
}
if (action != null) {
Intent notificationListenerIntent = new Intent(action);
notificationListenerIntent.putExtra("handle", deviceEvent.handle);
notificationListenerIntent.putExtra("title", deviceEvent.title);
if (deviceEvent.reply != null) {
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress());
String suffix = prefs.getString("canned_reply_suffix", null);
if (suffix != null && !Objects.equals(suffix, "")) {
deviceEvent.reply += suffix;
}
notificationListenerIntent.putExtra("reply", deviceEvent.reply);
}
LocalBroadcastManager.getInstance(context).sendBroadcast(notificationListenerIntent);
}
}
protected void handleGBDeviceEvent(GBDeviceEventBatteryInfo deviceEvent) {
Context context = getContext();
LOG.info("Got BATTERY_INFO device event");
gbDevice.setBatteryLevel(deviceEvent.level, deviceEvent.batteryIndex);
gbDevice.setBatteryState(deviceEvent.state, deviceEvent.batteryIndex);
gbDevice.setBatteryVoltage(deviceEvent.voltage, deviceEvent.batteryIndex);
final DevicePrefs devicePrefs = GBApplication.getDevicePrefs(gbDevice);
final BatteryConfig batteryConfig = gbDevice.getDeviceCoordinator().getBatteryConfig(gbDevice)[deviceEvent.batteryIndex];
if (deviceEvent.level == GBDevice.BATTERY_UNKNOWN) {
// no level available, just "high" or "low"
GB.removeBatteryFullNotification(context);
if (devicePrefs.getBatteryNotifyLowEnabled(batteryConfig) && BatteryState.BATTERY_LOW.equals(deviceEvent.state)) {
GB.updateBatteryLowNotification(context.getString(R.string.notif_battery_low, gbDevice.getAliasOrName()),
deviceEvent.extendedInfoAvailable() ?
context.getString(R.string.notif_battery_low_extended, gbDevice.getAliasOrName(),
context.getString(R.string.notif_battery_low_bigtext_last_charge_time, DateFormat.getDateTimeInstance().format(deviceEvent.lastChargeTime.getTime())) +
context.getString(R.string.notif_battery_low_bigtext_number_of_charges, String.valueOf(deviceEvent.numCharges)))
: ""
, context);
} else if (devicePrefs.getBatteryNotifyFullEnabled(batteryConfig) && BatteryState.BATTERY_CHARGING_FULL.equals(deviceEvent.state)) {
GB.removeBatteryLowNotification(context);
GB.updateBatteryFullNotification(context.getString(R.string.notif_battery_full, gbDevice.getAliasOrName()), "", context);
} else {
GB.removeBatteryLowNotification(context);
GB.removeBatteryFullNotification(context);
}
} else {
createStoreTask("Storing battery data", context, deviceEvent).execute();
final boolean batteryNotifyLowEnabled = devicePrefs.getBatteryNotifyLowEnabled(batteryConfig);
final boolean isBatteryLow = deviceEvent.level <= devicePrefs.getBatteryNotifyLowThreshold(batteryConfig) &&
(BatteryState.BATTERY_LOW.equals(deviceEvent.state) || BatteryState.BATTERY_NORMAL.equals(deviceEvent.state));
final boolean batteryNotifyFullEnabled = devicePrefs.getBatteryNotifyFullEnabled(batteryConfig);
final boolean isBatteryFull = deviceEvent.level >= devicePrefs.getBatteryNotifyFullThreshold(batteryConfig) &&
(BatteryState.BATTERY_CHARGING.equals(deviceEvent.state) || BatteryState.BATTERY_CHARGING_FULL.equals(deviceEvent.state));
//show the notification if the battery level is below threshold and only if not connected to charger
if (batteryNotifyLowEnabled && isBatteryLow) {
GB.removeBatteryFullNotification(context);
GB.updateBatteryLowNotification(context.getString(R.string.notif_battery_low_percent, gbDevice.getAliasOrName(), String.valueOf(deviceEvent.level)),
deviceEvent.extendedInfoAvailable() ?
context.getString(R.string.notif_battery_low_percent, gbDevice.getAliasOrName(), String.valueOf(deviceEvent.level)) + "\n" +
context.getString(R.string.notif_battery_low_bigtext_last_charge_time, DateFormat.getDateTimeInstance().format(deviceEvent.lastChargeTime.getTime())) +
context.getString(R.string.notif_battery_low_bigtext_number_of_charges, String.valueOf(deviceEvent.numCharges))
: ""
, context);
} else if (batteryNotifyFullEnabled && isBatteryFull) {
GB.removeBatteryLowNotification(context);
GB.updateBatteryFullNotification(context.getString(R.string.notif_battery_full, gbDevice.getAliasOrName()), "", context);
} else {
GB.removeBatteryLowNotification(context);
GB.removeBatteryFullNotification(context);
}
}
gbDevice.sendDeviceUpdateIntent(context);
}
/**
* Helper method to run specific actions configured in the device preferences, upon wear state
* or awake/asleep events.
*
* @param actions
* @param message
*/
private void handleDeviceAction(Set<String> actions, String message) {
if (actions.isEmpty()) {
return;
}
LOG.debug("Handing device actions: {}", TextUtils.join(",", actions));
final String actionBroadcast = getContext().getString(R.string.pref_device_action_broadcast_value);
final String actionFitnessControlStart = getContext().getString(R.string.pref_device_action_fitness_app_control_start_value);
final String actionFitnessControlStop = getContext().getString(R.string.pref_device_action_fitness_app_control_stop_value);
final String actionFitnessControlToggle = getContext().getString(R.string.pref_device_action_fitness_app_control_toggle_value);
final String actionMediaPlay = getContext().getString(R.string.pref_media_play_value);
final String actionMediaPause = getContext().getString(R.string.pref_media_pause_value);
final String actionMediaPlayPause = getContext().getString(R.string.pref_media_playpause_value);
final String actionDndOff = getContext().getString(R.string.pref_device_action_dnd_off_value);
final String actionDndpriority = getContext().getString(R.string.pref_device_action_dnd_priority_value);
final String actionDndAlarms = getContext().getString(R.string.pref_device_action_dnd_alarms_value);
final String actionDndOn = getContext().getString(R.string.pref_device_action_dnd_on_value);
if (actions.contains(actionBroadcast)) {
if (message != null) {
Intent in = new Intent();
in.setAction(message);
LOG.info("Sending broadcast {}", message);
getContext().getApplicationContext().sendBroadcast(in);
}
}
if (actions.contains(actionFitnessControlStart)) {
OpenTracksController.startRecording(getContext());
} else if (actions.contains(actionFitnessControlStop)) {
OpenTracksController.stopRecording(getContext());
} else if (actions.contains(actionFitnessControlToggle)) {
OpenTracksController.toggleRecording(getContext());
}
final String mediaAction;
if (actions.contains(actionMediaPlayPause)) {
mediaAction = actionMediaPlayPause;
} else if (actions.contains(actionMediaPause)) {
mediaAction = actionMediaPause;
} else if (actions.contains(actionMediaPlay)) {
mediaAction = actionMediaPlay;
} else {
mediaAction = null;
}
if (mediaAction != null) {
GBDeviceEventMusicControl deviceEventMusicControl = new GBDeviceEventMusicControl();
deviceEventMusicControl.event = GBDeviceEventMusicControl.Event.valueOf(mediaAction);
evaluateGBDeviceEvent(deviceEventMusicControl);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
final int interruptionFilter;
if (actions.contains(actionDndOff)) {
interruptionFilter = NotificationManager.INTERRUPTION_FILTER_ALL;
} else if (actions.contains(actionDndpriority)) {
interruptionFilter = NotificationManager.INTERRUPTION_FILTER_PRIORITY;
} else if (actions.contains(actionDndAlarms)) {
interruptionFilter = NotificationManager.INTERRUPTION_FILTER_ALARMS;
} else if (actions.contains(actionDndOn)) {
interruptionFilter = NotificationManager.INTERRUPTION_FILTER_NONE;
} else {
interruptionFilter = NotificationManager.INTERRUPTION_FILTER_UNKNOWN;
}
if (interruptionFilter != NotificationManager.INTERRUPTION_FILTER_UNKNOWN) {
LOG.debug("Setting do not disturb to {}", interruptionFilter);
if (!notificationManager.isNotificationPolicyAccessGranted()) {
LOG.warn("Do not disturb permissions not granted");
}
notificationManager.setInterruptionFilter(interruptionFilter);
}
}
}
private void handleGBDeviceEvent(GBDeviceEventSleepStateDetection event) {
LOG.debug("Got SLEEP_STATE_DETECTION device event, detected sleep state = {}", event.sleepState);
if (event.sleepState == SleepState.UNKNOWN) {
return;
}
String actionPreferenceKey, messagePreferenceKey;
int defaultBroadcastMessageResource;
switch (event.sleepState) {
case AWAKE:
actionPreferenceKey = DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_WOKE_UP_SELECTIONS;
messagePreferenceKey = DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_WOKE_UP_BROADCAST;
defaultBroadcastMessageResource = R.string.prefs_events_forwarding_wokeup_broadcast_default_value;
break;
case ASLEEP:
actionPreferenceKey = DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_FELL_SLEEP_SELECTIONS;
messagePreferenceKey = DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_FELL_SLEEP_BROADCAST;
defaultBroadcastMessageResource = R.string.prefs_events_forwarding_fellsleep_broadcast_default_value;
break;
default:
LOG.warn("Unable to deduce action and broadcast message preference key for sleep state {}", event.sleepState);
return;
}
Set<String> actions = getDevicePrefs().getStringSet(actionPreferenceKey, Collections.emptySet());
if (actions.isEmpty()) {
return;
}
String broadcastMessage = getDevicePrefs().getString(messagePreferenceKey, context.getString(defaultBroadcastMessageResource));
handleDeviceAction(actions, broadcastMessage);
}
private void handleGBDeviceEvent(GBDeviceEventWearState event) {
LOG.debug("Got WEAR_STATE device event, wearingState = {}", event.wearingState);
if (event.wearingState == WearingState.UNKNOWN) {
LOG.warn("WEAR_STATE state is UNKNOWN, aborting further evaluation");
return;
}
if (event.wearingState != WearingState.NOT_WEARING) {
LOG.debug("WEAR_STATE state is not NOT_WEARING, aborting further evaluation");
}
Set<String> actionOnUnwear = getDevicePrefs().getStringSet(
DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_START_NON_WEAR_SELECTIONS,
Collections.emptySet()
);
// check if an action is set
if (actionOnUnwear.isEmpty()) {
return;
}
String broadcastMessage = getDevicePrefs().getString(
DeviceSettingsPreferenceConst.PREF_DEVICE_ACTION_START_NON_WEAR_BROADCAST,
getContext().getString(R.string.prefs_events_forwarding_startnonwear_broadcast_default_value)
);
handleDeviceAction(actionOnUnwear, broadcastMessage);
}
private void handleGBDeviceEvent(GBDeviceMusicData deviceEvent) {
Context context = getContext();
LOG.info("Got event for ACTION_MUSIC_DATA");
Intent intent = new Intent(MusicManagerActivity.ACTION_MUSIC_DATA);
intent.putExtra("type", deviceEvent.type);
if(deviceEvent.list != null) {
ArrayList<GBDeviceMusic> list = new ArrayList<>(deviceEvent.list);
intent.putExtra("musicList", list);
}
if(deviceEvent.playlists != null) {
ArrayList<GBDeviceMusicPlaylist> list = new ArrayList<>(deviceEvent.playlists);
intent.putExtra("musicPlaylist", list);
}
if(!TextUtils.isEmpty(deviceEvent.deviceInfo)) {
intent.putExtra("deviceInfo", deviceEvent.deviceInfo);
}
if(deviceEvent.maxMusicCount > 0) {
intent.putExtra("maxMusicCount", deviceEvent.maxMusicCount);
}
if(deviceEvent.maxPlaylistCount > 0) {
intent.putExtra("maxPlaylistCount", deviceEvent.maxPlaylistCount);
}
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
private void handleGBDeviceEvent(GBDeviceMusicUpdate deviceEvent) {
Context context = getContext();
LOG.info("Got event for ACTION_MUSIC_UPDATE");
Intent intent = new Intent(MusicManagerActivity.ACTION_MUSIC_UPDATE);
intent.putExtra("success", deviceEvent.success);
intent.putExtra("operation", deviceEvent.operation);
intent.putExtra("playlistIndex", deviceEvent.playlistIndex);
intent.putExtra("playlistName", deviceEvent.playlistName);
intent.putExtra("musicIds", deviceEvent.musicIds);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
private StoreDataTask createStoreTask(String task, Context context, GBDeviceEventBatteryInfo deviceEvent) {
return new StoreDataTask(task, context, deviceEvent);
}
public class StoreDataTask extends DBAccess {
GBDeviceEventBatteryInfo deviceEvent;
public StoreDataTask(String task, Context context, GBDeviceEventBatteryInfo deviceEvent) {
super(task, context);
this.deviceEvent = deviceEvent;
}
@Override
protected void doInBackground(DBHandler handler) {
DaoSession daoSession = handler.getDaoSession();
Device device = DBHelper.getDevice(gbDevice, daoSession);
int ts = (int) (System.currentTimeMillis() / 1000);
BatteryLevel batteryLevel = new BatteryLevel();
batteryLevel.setTimestamp(ts);
batteryLevel.setBatteryIndex(deviceEvent.batteryIndex);
batteryLevel.setDevice(device);
batteryLevel.setLevel(deviceEvent.level);
handler.getDaoSession().getBatteryLevelDao().insert(batteryLevel);
}
}
public void handleGBDeviceEvent(GBDeviceEventDisplayMessage message) {
GB.log(message.message, message.severity, null);
Intent messageIntent = new Intent(GB.ACTION_DISPLAY_MESSAGE);
messageIntent.putExtra(GB.DISPLAY_MESSAGE_MESSAGE, message.message);
messageIntent.putExtra(GB.DISPLAY_MESSAGE_DURATION, message.duration);
messageIntent.putExtra(GB.DISPLAY_MESSAGE_SEVERITY, message.severity);
LocalBroadcastManager.getInstance(context).sendBroadcast(messageIntent);
public void handleGBDeviceEvent(final GBDeviceEvent deviceEvent) {
LOG.debug("Handling event: {}", deviceEvent);
deviceEvent.evaluate(getContext(), gbDevice);
}
public DevicePrefs getDevicePrefs() {
@@ -338,9 +338,9 @@ public class GarminSupport extends AbstractBTLEDeviceSupport implements ICommuni
if (!getKeepActivityDataOnDevice()) { // delete file from watch upon successful download
sendOutgoingMessage("archive file " + entry.getFileIndex(), new SetFileFlagsMessage(entry.getFileIndex(), SetFileFlagsMessage.FileFlags.ARCHIVE));
}
} else {
super.evaluateGBDeviceEvent(deviceEvent);
}
super.evaluateGBDeviceEvent(deviceEvent);
}
/** @noinspection BooleanMethodIsAlwaysInverted*/
@@ -1,9 +1,12 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents;
import android.content.Context;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.devices.vivomovehr.GarminCapability;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class CapabilitiesDeviceEvent extends GBDeviceEvent {
public Set<GarminCapability> capabilities;
@@ -11,4 +14,9 @@ public class CapabilitiesDeviceEvent extends GBDeviceEvent {
public CapabilitiesDeviceEvent(final Set<GarminCapability> capabilities) {
this.capabilities = capabilities;
}
@Override
public void evaluate(final Context context, final GBDevice device) {
// Handled in support class
}
}
@@ -1,9 +1,17 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents;
import android.content.Context;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileTransferHandler;
public class FileDownloadedDeviceEvent extends GBDeviceEvent {
public FileTransferHandler.DirectoryEntry directoryEntry;
public String localPath;
@Override
public void evaluate(final Context context, final GBDevice device) {
// Handled in support class
}
}
@@ -1,9 +1,15 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents;
import android.content.Context;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class NotificationSubscriptionDeviceEvent extends GBDeviceEvent {
public boolean enable;
@Override
public void evaluate(final Context context, final GBDevice device) {
// Handled in support class
}
}
@@ -1,12 +1,14 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents;
import android.content.Context;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.FileType;
public class SupportedFileTypesDeviceEvent extends GBDeviceEvent {
private final List<FileType> supportedFileTypes;
public SupportedFileTypesDeviceEvent(List<FileType> fileTypes) {
@@ -16,4 +18,9 @@ public class SupportedFileTypesDeviceEvent extends GBDeviceEvent {
public List<FileType> getSupportedFileTypes() {
return supportedFileTypes;
}
@Override
public void evaluate(final Context context, final GBDevice device) {
// Handled in support class
}
}
@@ -1,12 +1,16 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.deviceevents;
import android.content.Context;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
public class WeatherRequestDeviceEvent extends GBDeviceEvent {
private final int format;
private final int latitude;
private final int longitude;
private final int hoursOfForecast;
public WeatherRequestDeviceEvent(int format, int latitude, int longitude, int hoursOfForecast) {
this.format = format;
this.latitude = latitude;
@@ -14,5 +18,8 @@ public class WeatherRequestDeviceEvent extends GBDeviceEvent {
this.hoursOfForecast = hoursOfForecast;
}
@Override
public void evaluate(final Context context, final GBDevice device) {
// Handled in support class
}
}
@@ -740,11 +740,6 @@ public class QHybridSupport extends QHybridBaseSupport {
}
}
@Override
public void handleGBDeviceEvent(GBDeviceEventBatteryInfo deviceEvent){
super.handleGBDeviceEvent(deviceEvent);
}
@Override
public boolean onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
watchAdapter.onCharacteristicWrite(gatt, characteristic, status);
@@ -52,6 +52,8 @@ public class SonyHeadphonesSupport extends AbstractHeadphoneDeviceSupport {
SonyHeadphonesIoThread deviceIOThread = getDeviceIOThread();
deviceIOThread.write(sonyProtocol.getFromQueue());
}
return;
}
super.evaluateGBDeviceEvent(deviceEvent);
@@ -16,11 +16,14 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.deviceevents;
import android.content.Context;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
public class SonyHeadphonesEnqueueRequestEvent extends GBDeviceEvent {
@@ -37,4 +40,9 @@ public class SonyHeadphonesEnqueueRequestEvent extends GBDeviceEvent {
public List<Request> getRequests() {
return this.requests;
}
@Override
public void evaluate(final Context context, final GBDevice device) {
// Handled in support class
}
}