/* Copyright (C) 2015-2021 Andreas Böhler, Andreas Shimokawa, Carsten
Pfeiffer, Daniele Gobbetti, José Rebelo, Pauli Salmenrinne, Sebastian Kranz,
Taavi Eomäe
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see . */
package nodomain.freeyourgadget.gadgetbridge.service;
import android.app.Notification;
import android.app.PendingIntent;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
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.telephony.SmsManager;
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.Date;
import java.util.Locale;
import java.util.Objects;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.FindPhoneActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.appmanager.AbstractAppManagerFragment;
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.GBDeviceEventDisplayMessage;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFindPhone;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventFmFrequency;
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.entities.BatteryLevel;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.Contact;
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.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 static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_HIGH_PRIORITY_ID;
import static nodomain.freeyourgadget.gadgetbridge.util.GB.NOTIFICATION_CHANNEL_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.
/**
* Abstract implementation of DeviceSupport with some implementations for
* common functionality. Still transport independent.
*/
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;
private Context context;
private boolean autoReconnect;
@Override
public void setContext(GBDevice gbDevice, BluetoothAdapter btAdapter, Context context) {
this.gbDevice = gbDevice;
this.btAdapter = btAdapter;
this.context = context;
}
/**
* Default implementation just calls #connect()
*/
@Override
public boolean connectFirstTime() {
return connect();
}
@Override
public boolean isConnected() {
return gbDevice.isConnected();
}
/**
* Returns true if the device is not only connected, but also
* initialized.
*
* @see GBDevice#isInitialized()
*/
protected boolean isInitialized() {
return gbDevice.isInitialized();
}
@Override
public void setAutoReconnect(boolean enable) {
autoReconnect = enable;
}
@Override
public boolean getAutoReconnect() {
return autoReconnect;
}
@Override
public boolean getImplicitCallbackModify() {
return true;
}
@Override
public GBDevice getDevice() {
return gbDevice;
}
@Override
public BluetoothAdapter getBluetoothAdapter() {
return btAdapter;
}
@Override
public Context getContext() {
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 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 GBDeviceEventFmFrequency) {
handleGBDeviceEvent((GBDeviceEventFmFrequency) deviceEvent);
}
}
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);
LocalBroadcastManager.getInstance(context).sendBroadcast(intentVibrate);
break;
case RING:
final Intent intentRing = new Intent(FindPhoneActivity.ACTION_RING);
LocalBroadcastManager.getInstance(context).sendBroadcast(intentRing);
break;
case STOP:
final Intent intentStop = new Intent(FindPhoneActivity.ACTION_FOUND);
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");
Intent intent = new Intent(context, FindPhoneActivity.class);
intent.putExtra(FindPhoneActivity.EXTRA_RING, ring);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pi = PendingIntentUtils.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT, false);
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)
.setContentTitle( context.getString( R.string.find_my_phone_notification ) );
notification.setGroup("BackgroundService");
CompanionDeviceManager manager = (CompanionDeviceManager) context.getSystemService(Context.COMPANION_DEVICE_SERVICE);
if (manager.getAssociations().size() > 0) {
GB.notify(GB.NOTIFICATION_ID_PHONE_FIND, notification.build(), context);
context.startActivity(intent);
LOG.debug("CompanionDeviceManager associations were found, starting intent");
} else {
GB.notify(GB.NOTIFICATION_ID_PHONE_FIND, notification.build(), context);
LOG.warn("CompanionDeviceManager associations were not found, can't start 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(GBDeviceEventVersionInfo infoEvent) {
Context context = getContext();
LOG.info("Got event for VERSION_INFO: " + infoEvent);
if (gbDevice == null) {
return;
}
gbDevice.setFirmwareVersion(infoEvent.fwVersion);
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());
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());
}
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/*");
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 = (String) 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);
gbDevice.setBatteryVoltage(deviceEvent.voltage, deviceEvent.batteryIndex);
if (deviceEvent.level == GBDevice.BATTERY_UNKNOWN) {
// no level available, just "high" or "low"
if (BatteryState.BATTERY_LOW.equals(deviceEvent.state)) {
GB.updateBatteryNotification(context.getString(R.string.notif_battery_low, gbDevice.getName()),
deviceEvent.extendedInfoAvailable() ?
context.getString(R.string.notif_battery_low_extended, gbDevice.getName(),
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 {
GB.removeBatteryNotification(context);
}
} else {
createStoreTask("Storing battery data", context, deviceEvent).execute();
//show the notification if the battery level is below threshold and only if not connected to charger
if (deviceEvent.level <= gbDevice.getBatteryThresholdPercent() &&
(BatteryState.BATTERY_LOW.equals(deviceEvent.state) ||
BatteryState.BATTERY_NORMAL.equals(deviceEvent.state))
) {
GB.updateBatteryNotification(context.getString(R.string.notif_battery_low_percent, gbDevice.getName(), String.valueOf(deviceEvent.level)),
deviceEvent.extendedInfoAvailable() ?
context.getString(R.string.notif_battery_low_percent, gbDevice.getName(), 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 {
GB.removeBatteryNotification(context);
}
}
gbDevice.sendDeviceUpdateIntent(context);
}
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 String customStringFilter(String inputString) {
return inputString;
}
// Empty functions following, leaving optional implementation up to child classes
/**
* If the device supports a "find phone" functionality, this method can
* be overridden and implemented by the device support class.
* @param start true if starting the search, false if stopping
*/
@Override
public void onFindPhone(boolean start) {
}
/**
* If the device supports a "find device" functionality, this method can
* be overridden and implemented by the device support class.
* @param start true if starting the search, false if stopping
*/
@Override
public void onFindDevice(boolean start) {
}
/**
* If the device supports a "set FM frequency" functionality, this method
* can be overridden and implemented by the device support class.
* @param frequency the FM frequency to set
*/
@Override
public void onSetFmFrequency(float frequency) {
}
/**
* If the device supports a "set LED color" functionality, this method
* can be overridden and implemented by the device support class.
* @param color the new color, in ARGB, with alpha = 255
*/
@Override
public void onSetLedColor(int color) {
}
/**
* If the device can be turned off by sending a command, this method
* can be overridden and implemented by the device support class.
*/
@Override
public void onPowerOff() {
}
/**
* If the device has a functionality to set the phone volume, this method
* can be overridden and implemented by the device support class.
* @param volume the volume percentage (0 to 100).
*/
@Override
public void onSetPhoneVolume(final float volume) {
}
/**
* If the device can receive the GPS location from the phone, this method
* can be overridden and implemented by the device support class.
* @param location {@link android.location.Location} object containing the current GPS coordinates
*/
@Override
public void onSetGpsLocation(Location location) {
}
/**
* If reminders can be set on the device, this method can be
* overridden and implemented by the device support class.
* @param reminders {@link java.util.ArrayList} containing {@link nodomain.freeyourgadget.gadgetbridge.model.Reminder} instances
*/
@Override
public void onSetReminders(ArrayList extends Reminder> reminders) {
}
/**
* If loyalty cards can be set on the device, this method can be
* overridden and implemented by the device support class.
* @param cards {@link java.util.ArrayList} containing {@link LoyaltyCard} instances
*/
@Override
public void onSetLoyaltyCards(ArrayList cards) {
}
/**
* If world clocks can be configured on the device, this method can be
* overridden and implemented by the device support class.
* @param clocks {@link java.util.ArrayList} containing {@link nodomain.freeyourgadget.gadgetbridge.model.WorldClock} instances
*/
@Override
public void onSetWorldClocks(ArrayList extends WorldClock> clocks) {
}
/**
* If contacts can be configured on the device, this method can be
* overridden and implemented by the device support class.
* @param contacts {@link java.util.ArrayList} containing {@link nodomain.freeyourgadget.gadgetbridge.model.Contact} instances
*/
@Override
public void onSetContacts(ArrayList extends Contact> contacts) {
}
/**
* If the device can receive and display notifications, this method
* can be overridden and implemented by the device support class.
* @param notificationSpec notification details
*/
@Override
public void onNotification(NotificationSpec notificationSpec) {
}
/**
* If notifications can be deleted from the device, this method can be
* overridden and implemented by the device support class.
* @param id the unique notification identifier
*/
@Override
public void onDeleteNotification(int id) {
}
/**
* If the time can be set on the device, this method can be
* overridden and implemented by the device support class.
*/
@Override
public void onSetTime() {
}
/**
* If alarms can be set on the device, this method can be
* overridden and implemented by the device support class.
* @param alarms {@link java.util.ArrayList} containing {@link nodomain.freeyourgadget.gadgetbridge.model.Alarm} instances
*/
@Override
public void onSetAlarms(ArrayList extends Alarm> alarms) {
}
/**
* If the device can receive and show or handle phone call details, this
* method can be overridden and implemented by the device support class.
* @param callSpec the call state details
*/
@Override
public void onSetCallState(CallSpec callSpec) {
}
/**
* If the device has a "canned messages" functionality, this method
* can be overridden and implemented by the device support class.
* @param cannedMessagesSpec the canned messages to send to the device
*/
@Override
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
}
/**
* If the music play state can be set on the device, this method
* can be overridden and implemented by the device support class.
* @param stateSpec the current state of music playback
*/
@Override
public void onSetMusicState(MusicStateSpec stateSpec) {
}
/**
* If the music information can be shown on the device, this method can be
* overridden and implemented by the device support class.
* @param musicSpec the current music information, like track name and artist
*/
@Override
public void onSetMusicInfo(MusicSpec musicSpec) {
}
/**
* If apps can be installed on the device, this method can be
* overridden and implemented by the device support class.
* @param uri reference to a watch app file
*/
@Override
public void onInstallApp(Uri uri) {
}
/**
* If the list of apps on the device can be retrieved, this method
* can be overridden and implemented by the device support class.
*/
@Override
public void onAppInfoReq() {
}
/**
* If the device supports starting an app with a command, this method
* can be overridden and implemented by the device support class.
* @param uuid the Gadgetbridge internal UUID of the app
* @param start true to start, false to stop the app (if supported)
*/
@Override
public void onAppStart(UUID uuid, boolean start) {
}
/**
* If apps can be downloaded from the device, this method can be
* overridden and implemented by the device support class.
* @param uuid the Gadgetbridge internal UUID of the app
*/
@Override
public void onAppDownload(UUID uuid) {
}
/**
* If apps on the device can be deleted with a command, this method
* can be overridden and implemented by the device support class.
* @param uuid the Gadgetbridge internal UUID of the app
*/
@Override
public void onAppDelete(UUID uuid) {
}
/**
* If apps on the device can be configured, this method can be
* overridden and implemented by the device support class.
* @param appUuid the Gadgetbridge internal UUID of the app
* @param config the configuration of the app
* @param id
*/
@Override
public void onAppConfiguration(UUID appUuid, String config, Integer id) {
}
/**
* If apps on the device can be reordered, this method can be
* overridden and implemented by the device support class.
* @param uuids array of Gadgetbridge internal UUIDs of the apps
*/
@Override
public void onAppReorder(UUID[] uuids) {
}
/**
* If recorded data can be fetched from the device, this method
* can be overridden and implemented by the device support class.
* @param dataTypes which data types to fetch
*/
@Override
public void onFetchRecordedData(int dataTypes) {
}
/**
* If a device can be reset with a command, this method can be
* overridden and implemented by the device support class.
* @param flags can be used to pass flags with the reset command
*/
@Override
public void onReset(int flags) {
}
/**
* If the device can perform a heart rate measurement on request, this
* method can be overridden and implemented by the device support class.
*/
@Override
public void onHeartRateTest() {
}
/**
* If the device has the functionality to enable/disable realtime heart rate measurement,
* this method can be overridden and implemented by the device support class.
* @param enable true to enable, false to disable realtime heart rate measurement
*/
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
}
/**
* If the device has the functionality to enable/disable realtime steps information,
* this method can be overridden and implemented by the device support class.
* @param enable true to enable, false to disable realtime steps
*/
@Override
public void onEnableRealtimeSteps(boolean enable) {
}
/**
* If the device has a functionality to enable constant vibration, this
* method can be overridden and implemented by the device support class.
* @param integer the vibration intensity
*/
@Override
public void onSetConstantVibration(int integer) {
}
/**
* If the device supports taking screenshots of the screen, this method can
* be overridden and implemented by the device support class.
*/
@Override
public void onScreenshotReq() {
}
/**
* If the device has a toggle to enable the use of heart rate for sleep detection,
* this method can be overridden and implemented by the device support class.
* @param enable true to enable, false to disable using heart rate for sleep detection
*/
@Override
public void onEnableHeartRateSleepSupport(boolean enable) {
}
/**
* If the heart rate measurement interval can be changed on the device,
* this method can be overridden and implemented by the device support class.
* @param seconds the interval to configure on the device
*/
@Override
public void onSetHeartRateMeasurementInterval(int seconds) {
}
/**
* If calendar events can be sent to the device, this method can be
* overridden and implemented by the device support class.
* @param calendarEventSpec calendar event details
*/
@Override
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
}
/**
* If calendar events can be deleted from the device, this method can
* be overridden and implemented by the device support class.
* @param type type of calendar event
* @param id id of calendar event
*/
@Override
public void onDeleteCalendarEvent(byte type, long id) {
}
/**
* If configuration options can be set on the device, this method
* can be overridden and implemented by the device support class.
* @param config the device specific option to set on the device
*/
@Override
public void onSendConfiguration(String config) {
}
/**
* If the configuration can be retrieved from the device, this method
* can be overridden and implemented by the device support class.
* @param config the device specific option to get from the device
*/
@Override
public void onReadConfiguration(String config) {
}
/**
* If the device can receive weather information, this method can be
* overridden and implemented by the device support class.
* @param weatherSpec weather information
*/
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
}
/**
* For testing new features, this method can be overridden and
* implemented by the device support class.
* It's called by clicking the "test new functionality" button
* in the Debug menu.
*/
@Override
public void onTestNewFunction() {
}
@Override
public void onSetNavigationInfo(NavigationInfoSpec navigationInfoSpec) {
}
}