Initial HERE Active Listening Device Support

master
Nicolò Balzarotti 2017-10-08 00:14:43 +02:00
parent 4b4e95bfe0
commit ac07ddc932
34 changed files with 1109 additions and 155 deletions

View File

@ -388,7 +388,10 @@
android:name=".activities.VibrationActivity" android:name=".activities.VibrationActivity"
android:label="@string/title_activity_vibration" android:label="@string/title_activity_vibration"
android:parentActivityName=".activities.ControlCenterv2" /> android:parentActivityName=".activities.ControlCenterv2" />
<activity
android:name=".activities.AudioActivity"
android:label="@string/title_audio_activity"
android:parentActivityName=".activities.ControlCenterv2" />
<provider <provider
android:name=".contentprovider.PebbleContentProvider" android:name=".contentprovider.PebbleContentProvider"
android:authorities="com.getpebble.android.provider" android:authorities="com.getpebble.android.provider"

View File

@ -0,0 +1,67 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.os.Bundle;
import android.widget.SeekBar;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.here.HereConstants;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
public class AudioActivity extends AbstractGBActivity {
private static final Logger LOG = LoggerFactory.getLogger(AudioActivity.class);
private SeekBar seekBar;
private int volume;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.audio_settings);
LOG.info("Create");
seekBar = (SeekBar) findViewById(R.id.volume_seekbar);
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int position, boolean fromUser) {
// HERE's volume range is from 0xe3 (-22dB on their app) to 0xff (+6dB)
// 0xff - 0xe3 = 28 -> seekbar max value
// volume = seekbar + Min_volume (= 0xe3 = 227)
volume = position + 227;
LOG.debug("Volume = " + (byte)volume + " = " + volume + "= " + position);
GBApplication.deviceService().onSetAudioProperty(0, volume);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
}

View File

@ -5,17 +5,17 @@
Gadgetbridge is free software: you can redistribute it and/or modify Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published 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 by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful, Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */ along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.adapter; package nodomain.freeyourgadget.gadgetbridge.adapter;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
@ -42,10 +42,12 @@ import android.widget.Toast;
import java.util.List; import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBEnvironment;
import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms; import nodomain.freeyourgadget.gadgetbridge.activities.ConfigureAlarms;
import nodomain.freeyourgadget.gadgetbridge.activities.VibrationActivity; import nodomain.freeyourgadget.gadgetbridge.activities.VibrationActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity; import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.AudioActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager; import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
@ -84,27 +86,27 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
holder.container.setOnClickListener(new View.OnClickListener() { holder.container.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (device.isInitialized() || device.isConnected()) { if (device.isInitialized() || device.isConnected()) {
showTransientSnackbar(R.string.controlcenter_snackbar_need_longpress); showTransientSnackbar(R.string.controlcenter_snackbar_need_longpress);
} else { } else {
showTransientSnackbar(R.string.controlcenter_snackbar_connecting); showTransientSnackbar(R.string.controlcenter_snackbar_connecting);
GBApplication.deviceService().connect(device); GBApplication.deviceService().connect(device);
} }
} }
}); });
holder.container.setOnLongClickListener(new View.OnLongClickListener() { holder.container.setOnLongClickListener(new View.OnLongClickListener() {
@Override @Override
public boolean onLongClick(View v) { public boolean onLongClick(View v) {
if (device.getState() != GBDevice.State.NOT_CONNECTED) { if (device.getState() != GBDevice.State.NOT_CONNECTED) {
showTransientSnackbar(R.string.controlcenter_snackbar_disconnecting); showTransientSnackbar(R.string.controlcenter_snackbar_disconnecting);
GBApplication.deviceService().disconnect(); GBApplication.deviceService().disconnect();
} }
return true; return true;
} }
}); });
holder.deviceImageView.setImageResource(R.drawable.level_list_device); holder.deviceImageView.setImageResource(R.drawable.level_list_device);
//level-list does not allow negative values, hence we always add 100 to the key. //level-list does not allow negative values, hence we always add 100 to the key.
holder.deviceImageView.setImageLevel(device.getType().getKey() + 100 + (device.isInitialized() ? 100 : 0)); holder.deviceImageView.setImageLevel(device.getType().getKey() + 100 + (device.isInitialized() ? 100 : 0));
@ -128,7 +130,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
holder.batteryStatusLabel.setText(device.getBatteryLevel() + "%"); holder.batteryStatusLabel.setText(device.getBatteryLevel() + "%");
BatteryState batteryState = device.getBatteryState(); BatteryState batteryState = device.getBatteryState();
if (BatteryState.BATTERY_CHARGING.equals(batteryState) || if (BatteryState.BATTERY_CHARGING.equals(batteryState) ||
BatteryState.BATTERY_CHARGING_FULL.equals(batteryState)) { BatteryState.BATTERY_CHARGING_FULL.equals(batteryState)) {
holder.batteryIcon.setImageLevel(device.getBatteryLevel() + 100); holder.batteryIcon.setImageLevel(device.getBatteryLevel() + 100);
} else { } else {
holder.batteryIcon.setImageLevel(device.getBatteryLevel()); holder.batteryIcon.setImageLevel(device.getBatteryLevel());
@ -139,76 +141,92 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
holder.fetchActivityDataBox.setVisibility((device.isInitialized() && coordinator.supportsActivityDataFetching()) ? View.VISIBLE : View.GONE); holder.fetchActivityDataBox.setVisibility((device.isInitialized() && coordinator.supportsActivityDataFetching()) ? View.VISIBLE : View.GONE);
holder.fetchActivityData.setOnClickListener(new View.OnClickListener() holder.fetchActivityData.setOnClickListener(new View.OnClickListener()
{ {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
showTransientSnackbar(R.string.busy_task_fetch_activity_data); showTransientSnackbar(R.string.busy_task_fetch_activity_data);
GBApplication.deviceService().onFetchActivityData(); GBApplication.deviceService().onFetchActivityData();
} }
} }
); );
//take screenshot //take screenshot
holder.takeScreenshotView.setVisibility((device.isInitialized() && coordinator.supportsScreenshots()) ? View.VISIBLE : View.GONE); holder.takeScreenshotView.setVisibility((device.isInitialized() && coordinator.supportsScreenshots()) ? View.VISIBLE : View.GONE);
holder.takeScreenshotView.setOnClickListener(new View.OnClickListener() holder.takeScreenshotView.setOnClickListener(new View.OnClickListener()
{ {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
showTransientSnackbar(R.string.controlcenter_snackbar_requested_screenshot); showTransientSnackbar(R.string.controlcenter_snackbar_requested_screenshot);
GBApplication.deviceService().onScreenshotReq(); GBApplication.deviceService().onScreenshotReq();
} }
} }
); );
//manage apps //manage apps
holder.manageAppsView.setVisibility((device.isInitialized() && coordinator.supportsAppsManagement()) ? View.VISIBLE : View.GONE); holder.manageAppsView.setVisibility((device.isInitialized() && coordinator.supportsAppsManagement()) ? View.VISIBLE : View.GONE);
holder.manageAppsView.setOnClickListener(new View.OnClickListener() holder.manageAppsView.setOnClickListener(new View.OnClickListener()
{ {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device); DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
Class<? extends Activity> appsManagementActivity = coordinator.getAppsManagementActivity(); Class<? extends Activity> appsManagementActivity = coordinator.getAppsManagementActivity();
if (appsManagementActivity != null) { if (appsManagementActivity != null) {
Intent startIntent = new Intent(context, appsManagementActivity); Intent startIntent = new Intent(context, appsManagementActivity);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device); startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
context.startActivity(startIntent); context.startActivity(startIntent);
} }
} }
} }
); );
//set alarms //set alarms
holder.setAlarmsView.setVisibility(coordinator.supportsAlarmConfiguration() ? View.VISIBLE : View.GONE); holder.setAlarmsView.setVisibility(coordinator.supportsAlarmConfiguration() ? View.VISIBLE : View.GONE);
holder.setAlarmsView.setOnClickListener(new View.OnClickListener() holder.setAlarmsView.setOnClickListener(new View.OnClickListener()
{ {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Intent startIntent; Intent startIntent;
startIntent = new Intent(context, ConfigureAlarms.class); startIntent = new Intent(context, ConfigureAlarms.class);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device); startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
context.startActivity(startIntent); context.startActivity(startIntent);
} }
} }
); );
//show graphs //show graphs
holder.showActivityGraphs.setVisibility(coordinator.supportsActivityTracking() ? View.VISIBLE : View.GONE); holder.showActivityGraphs.setVisibility(coordinator.supportsActivityTracking() ? View.VISIBLE : View.GONE);
holder.showActivityGraphs.setOnClickListener(new View.OnClickListener() holder.showActivityGraphs.setOnClickListener(new View.OnClickListener()
{ {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Intent startIntent; Intent startIntent;
startIntent = new Intent(context, ChartsActivity.class); startIntent = new Intent(context, ChartsActivity.class);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device); startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
context.startActivity(startIntent); context.startActivity(startIntent);
} }
} }
); );
// audio settings
holder.showAudioSettings.setVisibility(device.isInitialized() && coordinator.supportsAudioSettings() ? View.VISIBLE : View.GONE);
holder.showAudioSettings.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
showTransientSnackbar(R.string.controlcenter_snackbar_requested_screenshot);
Intent startIntent;
startIntent = new Intent(context, AudioActivity.class);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
context.startActivity(startIntent);
}
}
);
ItemWithDetailsAdapter infoAdapter = new ItemWithDetailsAdapter(context, device.getDeviceInfos()); ItemWithDetailsAdapter infoAdapter = new ItemWithDetailsAdapter(context, device.getDeviceInfos());
infoAdapter.setHorizontalAlignment(true); infoAdapter.setHorizontalAlignment(true);
@ -222,95 +240,96 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
holder.deviceInfoBox.setActivated(detailsShown); holder.deviceInfoBox.setActivated(detailsShown);
holder.deviceInfoBox.setVisibility(detailsShown ? View.VISIBLE : View.GONE); holder.deviceInfoBox.setVisibility(detailsShown ? View.VISIBLE : View.GONE);
holder.deviceInfoView.setOnClickListener(new View.OnClickListener() { holder.deviceInfoView.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
expandedDevicePosition = detailsShown ? -1 : position; expandedDevicePosition = detailsShown ? -1 : position;
TransitionManager.beginDelayedTransition(parent); TransitionManager.beginDelayedTransition(parent);
notifyDataSetChanged(); notifyDataSetChanged();
} }
} }
); );
holder.findDevice.setVisibility(device.isInitialized() ? View.VISIBLE : View.GONE); holder.findDevice.setVisibility((device.isInitialized() &&
device.getType() != DeviceType.HERE) ? View.VISIBLE : View.GONE);
holder.findDevice.setOnClickListener(new View.OnClickListener() holder.findDevice.setOnClickListener(new View.OnClickListener()
{ {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (device.getType() == DeviceType.VIBRATISSIMO) { if (device.getType() == DeviceType.VIBRATISSIMO) {
Intent startIntent; Intent startIntent;
startIntent = new Intent(context, VibrationActivity.class); startIntent = new Intent(context, VibrationActivity.class);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device); startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
context.startActivity(startIntent); context.startActivity(startIntent);
return; return;
} }
GBApplication.deviceService().onFindDevice(true); GBApplication.deviceService().onFindDevice(true);
//TODO: extract string resource if we like this solution. //TODO: extract string resource if we like this solution.
Snackbar.make(parent, R.string.control_center_find_lost_device, Snackbar.LENGTH_INDEFINITE).setAction("Found it!", new View.OnClickListener() { Snackbar.make(parent, R.string.control_center_find_lost_device, Snackbar.LENGTH_INDEFINITE).setAction("Found it!", new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
GBApplication.deviceService().onFindDevice(false); GBApplication.deviceService().onFindDevice(false);
} }
}).setCallback(new Snackbar.Callback() { }).setCallback(new Snackbar.Callback() {
@Override @Override
public void onDismissed(Snackbar snackbar, int event) { public void onDismissed(Snackbar snackbar, int event) {
GBApplication.deviceService().onFindDevice(false); GBApplication.deviceService().onFindDevice(false);
super.onDismissed(snackbar, event); super.onDismissed(snackbar, event);
} }
}).show(); }).show();
// ProgressDialog.show( // ProgressDialog.show(
// context, // context,
// context.getString(R.string.control_center_find_lost_device), // context.getString(R.string.control_center_find_lost_device),
// context.getString(R.string.control_center_cancel_to_stop_vibration), // context.getString(R.string.control_center_cancel_to_stop_vibration),
// true, true, // true, true,
// new DialogInterface.OnCancelListener() { // new DialogInterface.OnCancelListener() {
// @Override // @Override
// public void onCancel(DialogInterface dialog) { // public void onCancel(DialogInterface dialog) {
// GBApplication.deviceService().onFindDevice(false); // GBApplication.deviceService().onFindDevice(false);
// } // }
// }); // });
} }
} }
); );
//remove device, hidden under details //remove device, hidden under details
holder.removeDevice.setOnClickListener(new View.OnClickListener() holder.removeDevice.setOnClickListener(new View.OnClickListener()
{ {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
new AlertDialog.Builder(context) new AlertDialog.Builder(context)
.setCancelable(true) .setCancelable(true)
.setTitle(context.getString(R.string.controlcenter_delete_device_name, device.getName())) .setTitle(context.getString(R.string.controlcenter_delete_device_name, device.getName()))
.setMessage(R.string.controlcenter_delete_device_dialogmessage) .setMessage(R.string.controlcenter_delete_device_dialogmessage)
.setPositiveButton(R.string.Delete, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.Delete, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
try { try {
DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device); DeviceCoordinator coordinator = DeviceHelper.getInstance().getCoordinator(device);
if (coordinator != null) { if (coordinator != null) {
coordinator.deleteDevice(device); coordinator.deleteDevice(device);
} }
DeviceHelper.getInstance().removeBond(device); DeviceHelper.getInstance().removeBond(device);
} catch (Exception ex) { } catch (Exception ex) {
GB.toast(context, "Error deleting device: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex); GB.toast(context, "Error deleting device: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR, ex);
} finally { } finally {
Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST); Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent); LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent);
} }
} }
}) })
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() { .setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
// do nothing // do nothing
} }
}) })
.show(); .show();
} }
}); });
} }
@ -338,6 +357,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
ImageView manageAppsView; ImageView manageAppsView;
ImageView setAlarmsView; ImageView setAlarmsView;
ImageView showActivityGraphs; ImageView showActivityGraphs;
ImageView showAudioSettings;
ImageView deviceInfoView; ImageView deviceInfoView;
//overflow //overflow
@ -365,6 +385,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
manageAppsView = (ImageView) view.findViewById(R.id.device_action_manage_apps); manageAppsView = (ImageView) view.findViewById(R.id.device_action_manage_apps);
setAlarmsView = (ImageView) view.findViewById(R.id.device_action_set_alarms); setAlarmsView = (ImageView) view.findViewById(R.id.device_action_set_alarms);
showActivityGraphs = (ImageView) view.findViewById(R.id.device_action_show_activity_graphs); showActivityGraphs = (ImageView) view.findViewById(R.id.device_action_show_activity_graphs);
showAudioSettings = (ImageView) view.findViewById(R.id.device_action_show_audio_settings);
deviceInfoView = (ImageView) view.findViewById(R.id.device_info_image); deviceInfoView = (ImageView) view.findViewById(R.id.device_info_image);
deviceInfoBox = (RelativeLayout) view.findViewById(R.id.device_item_infos_box); deviceInfoBox = (RelativeLayout) view.findViewById(R.id.device_item_infos_box);
@ -429,7 +450,7 @@ public class GBDeviceAdapterv2 extends RecyclerView.Adapter<GBDeviceAdapterv2.Vi
View snackbarView = snackbar.getView(); View snackbarView = snackbar.getView();
// change snackbar text color // change snackbar text color
int snackbarTextId = android.support.design.R.id.snackbar_text; int snackbarTextId = android.support.design.R.id.snackbar_text;
TextView textView = (TextView) snackbarView.findViewById(snackbarTextId); TextView textView = (TextView) snackbarView.findViewById(snackbarTextId);
//textView.setTextColor(); //textView.setTextColor();

View File

@ -148,6 +148,14 @@ public interface DeviceCoordinator {
*/ */
boolean supportsActivityTracking(); boolean supportsActivityTracking();
/**
* Returns true if audio settings is supported by the device
* (with this coordinator). Only HERE is supported right now
*
* @return
*/
boolean supportsAudioSettings();
/** /**
* Returns true if activity data fetching is supported AND possible at this * Returns true if activity data fetching is supported AND possible at this
* very moment. This will consider the device state (being connected/disconnected/busy...) * very moment. This will consider the device state (being connected/disconnected/busy...)

View File

@ -79,6 +79,8 @@ public interface EventHandler {
void onSetConstantVibration(int integer); void onSetConstantVibration(int integer);
void onSetAudioProperty(int property, int volume);
void onScreenshotReq(); void onScreenshotReq();
void onEnableHeartRateSleepSupport(boolean enable); void onEnableHeartRateSleepSupport(boolean enable);

View File

@ -137,6 +137,11 @@ public class UnknownDeviceCoordinator extends AbstractDeviceCoordinator {
return false; return false;
} }
@Override
public boolean supportsAudioSettings() {
return false;
}
@Override @Override
public boolean supportsScreenshots() { public boolean supportsScreenshots() {
return false; return false;

View File

@ -0,0 +1,25 @@
package nodomain.freeyourgadget.gadgetbridge.devices.here;
/*
* @author Nicolò Balzarotti &lt;nicolo@nixo.xyz&gt;
*/
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID;
public final class HereConstants {
public static final String BASE_HERE_UUID = "d973f2%s-b19e-11e2-9e96-0800200c9a66";
public static final UUID UUID_CHARACTERISTIC_BATTERY = UUID.fromString(String.format(BASE_UUID, "2A19"));
public static final UUID UUID_BATTERY_VALUE = UUID.fromString(String.format(BASE_UUID, "180F"));
public static final UUID UUID_AUDIO_SETTINGS = UUID.fromString(String.format(BASE_HERE_UUID, "e0"));
public static final UUID UUID_CHARACTERISTIC_INFO = UUID.fromString(String.format(BASE_UUID, "180A"));
public static final UUID UUID_FW_VERSION = UUID.fromString(String.format(BASE_UUID, "2A26"));
public static final UUID UUID_VOLUME = UUID.fromString(String.format(BASE_HERE_UUID, "e7"));
// TODO: Put device in sleep mode, (byte)0x00 sleep, (byte)0x01 wake
public static final UUID UUID_SLEEP = UUID.fromString(String.format(BASE_HERE_UUID, "eb"));
}

View File

@ -0,0 +1,152 @@
/* Copyright (C) 2016-2017 Andreas Shimokawa, Carsten Pfeiffer, Daniele
Gobbetti
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.here;
import android.app.Activity;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.NonNull;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AudioActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import java.util.regex.Pattern; // Regex match name
import java.util.regex.Matcher; // Regex match name
public class HereCoordinator extends AbstractDeviceCoordinator {
@Override
public DeviceType getSupportedType(GBDeviceCandidate candidate) {
String name = candidate.getDevice().getName();
Pattern pat = Pattern.compile("H([LR])[A-F0-9]+|HERE-([LR])-[A-F0-9]+");
Matcher mat = pat.matcher(name);
// Identify if device is Left or Right
if (mat.matches()) {
// get device type (L or R)
// disabled until we find differences we need to handle
// if (mat.group(1).equals("L")) {
// return DeviceType.HEREL;
// } else if (mat.group(1).equals("R")) {
// return DeviceType.HERER;
// } else {
// Could not detect if L or R
return DeviceType.HERE;
// }
}
return DeviceType.UNKNOWN;
}
@Override
public DeviceType getDeviceType() {
return DeviceType.HERE;
}
@Override
public Class<? extends Activity> getPairingActivity() {
return null;
}
@Override
public Class<? extends Activity> getPrimaryActivity() {
return AudioActivity.class;
}
@Override
public InstallHandler findInstallHandler(Uri uri, Context context) {
return null;
}
@Override
public boolean supportsActivityDataFetching() {
return false;
}
@Override
public boolean supportsActivityTracking() {
return false;
}
@Override
public boolean supportsAudioSettings() {
return true;
}
@Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return null;
}
@Override
public boolean supportsScreenshots() {
return false;
}
@Override
public boolean supportsAlarmConfiguration() {
return false;
}
@Override
public boolean supportsSmartWakeup(GBDevice device) {
return false;
}
@Override
public boolean supportsHeartRateMeasurement(GBDevice device) {
return false;
}
@Override
public String getManufacturer() {
return "Doppler Labs";
}
@Override
public boolean supportsAppsManagement() {
return false;
}
@Override
public Class<? extends Activity> getAppsManagementActivity() {
return null;
}
@Override
public boolean supportsCalendarEvents() {
return false;
}
@Override
public boolean supportsRealtimeData() {
return false; // hmmm well, it has a temperature sensor :D
}
@Override
protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException {
// nothing to delete, yet
}
}

View File

@ -128,6 +128,11 @@ public class HPlusCoordinator extends AbstractDeviceCoordinator {
return true; return true;
} }
@Override
public boolean supportsAudioSettings() {
return false;
}
@Override @Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) { public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return new HPlusHealthSampleProvider(device, session); return new HPlusHealthSampleProvider(device, session);

View File

@ -114,6 +114,11 @@ public class TeclastH30Coordinator extends AbstractDeviceCoordinator {
return false; return false;
} }
@Override
public boolean supportsAudioSettings() {
return false;
}
@Override @Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) { public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return null; return null;

View File

@ -74,6 +74,11 @@ public class LiveviewCoordinator extends AbstractDeviceCoordinator {
return false; return false;
} }
@Override
public boolean supportsAudioSettings() {
return false;
}
@Override @Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) { public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return null; return null;

View File

@ -151,6 +151,11 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
return true; return true;
} }
@Override
public boolean supportsAudioSettings() {
return false;
}
@Override @Override
public String getManufacturer() { public String getManufacturer() {
return "Xiaomi"; return "Xiaomi";

View File

@ -98,6 +98,11 @@ public class No1F1Coordinator extends AbstractDeviceCoordinator {
return true; return true;
} }
@Override
public boolean supportsAudioSettings() {
return false;
}
@Override @Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) { public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return new No1F1SampleProvider(device, session); return new No1F1SampleProvider(device, session);

View File

@ -117,6 +117,11 @@ public class PebbleCoordinator extends AbstractDeviceCoordinator {
return true; return true;
} }
@Override
public boolean supportsAudioSettings() {
return false;
}
@Override @Override
public boolean supportsScreenshots() { public boolean supportsScreenshots() {
return true; return true;

View File

@ -75,6 +75,11 @@ public class VibratissimoCoordinator extends AbstractDeviceCoordinator {
return false; return false;
} }
@Override
public boolean supportsAudioSettings() {
return false;
}
@Override @Override
public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) { public SampleProvider<? extends ActivitySample> getSampleProvider(GBDevice device, DaoSession session) {
return null; return null;

View File

@ -24,6 +24,9 @@ import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.provider.ContactsContract; import android.provider.ContactsContract;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
// FIXME: REMOVE
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.UUID; import java.util.UUID;
@ -46,6 +49,9 @@ import static nodomain.freeyourgadget.gadgetbridge.util.JavaExtensions.coalesce;
public class GBDeviceService implements DeviceService { public class GBDeviceService implements DeviceService {
protected final Context mContext; protected final Context mContext;
// FIXME: REMOVE
private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.class);
private final Class<? extends Service> mServiceClass; private final Class<? extends Service> mServiceClass;
private final String[] transliterationExtras = new String[]{ private final String[] transliterationExtras = new String[]{
EXTRA_NOTIFICATION_PHONENUMBER, EXTRA_NOTIFICATION_PHONENUMBER,
@ -298,6 +304,14 @@ public class GBDeviceService implements DeviceService {
invokeService(intent); invokeService(intent);
} }
@Override
public void onSetAudioProperty(int property, int intensity) {
// volume = 0
Intent intent = createIntent().setAction(ACTION_SET_AUDIO_PROPERTY)
.putExtra(EXTRA_AUDIO_PROPERTY, intensity);
invokeService(intent);
}
@Override @Override
public void onScreenshotReq() { public void onScreenshotReq() {
Intent intent = createIntent().setAction(ACTION_REQUEST_SCREENSHOT); Intent intent = createIntent().setAction(ACTION_REQUEST_SCREENSHOT);

View File

@ -53,6 +53,7 @@ public interface DeviceService extends EventHandler {
String ACTION_DISCONNECT = PREFIX + ".action.disconnect"; String ACTION_DISCONNECT = PREFIX + ".action.disconnect";
String ACTION_FIND_DEVICE = PREFIX + ".action.find_device"; String ACTION_FIND_DEVICE = PREFIX + ".action.find_device";
String ACTION_SET_CONSTANT_VIBRATION = PREFIX + ".action.set_constant_vibration"; String ACTION_SET_CONSTANT_VIBRATION = PREFIX + ".action.set_constant_vibration";
String ACTION_SET_AUDIO_PROPERTY = PREFIX + ".action.set_audio_property";
String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms"; String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms";
String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps"; String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps";
String ACTION_REALTIME_SAMPLES = PREFIX + ".action.realtime_samples"; String ACTION_REALTIME_SAMPLES = PREFIX + ".action.realtime_samples";
@ -76,6 +77,7 @@ public interface DeviceService extends EventHandler {
String EXTRA_NOTIFICATION_PEBBLE_COLOR = "notification_pebble_color"; String EXTRA_NOTIFICATION_PEBBLE_COLOR = "notification_pebble_color";
String EXTRA_FIND_START = "find_start"; String EXTRA_FIND_START = "find_start";
String EXTRA_VIBRATION_INTENSITY = "vibration_intensity"; String EXTRA_VIBRATION_INTENSITY = "vibration_intensity";
String EXTRA_AUDIO_PROPERTY = "audio_property";
String EXTRA_CALL_COMMAND = "call_command"; String EXTRA_CALL_COMMAND = "call_command";
String EXTRA_CALL_PHONENUMBER = "call_phonenumber"; String EXTRA_CALL_PHONENUMBER = "call_phonenumber";
String EXTRA_CALL_DISPLAYNAME = "call_displayname"; String EXTRA_CALL_DISPLAYNAME = "call_displayname";

View File

@ -40,6 +40,11 @@ public enum DeviceType {
EXRIZUK8(42, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled), EXRIZUK8(42, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled),
NO1F1(50, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled), NO1F1(50, R.drawable.ic_device_hplus, R.drawable.ic_device_hplus_disabled),
TECLASTH30(60, R.drawable.ic_device_h30_h10, R.drawable.ic_device_h30_h10_disabled), TECLASTH30(60, R.drawable.ic_device_h30_h10, R.drawable.ic_device_h30_h10_disabled),
// Single device made of L + R (71, 71)
// but each earbut can be controlled independently
HERE(70, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled),
// HERER(71, R.drawable.ic_device_here, R.drawable.ic_device_here_disabled),
// HEREL(72, R.drawable.ic_device_here, R.drawable.ic_device_here_disabled),
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled); TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled);
private final int key; private final int key;

View File

@ -100,10 +100,12 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SE
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETTIME; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SETTIME;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_ALARMS; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_ALARMS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_CONSTANT_VIBRATION; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_CONSTANT_VIBRATION;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_SET_AUDIO_PROPERTY;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_START; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_START;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_STARTAPP; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_STARTAPP;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_TEST_NEW_FUNCTION; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_TEST_NEW_FUNCTION;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_ALARMS; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_ALARMS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_AUDIO_PROPERTY;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_CONFIG; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_CONFIG;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_START; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_START;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_UUID; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_UUID;
@ -123,6 +125,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CAN
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONFIG; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONFIG;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONNECT_FIRST_TIME; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CONNECT_FIRST_TIME;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FIND_START; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FIND_START;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ALBUM; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ALBUM;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ARTIST; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ARTIST;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_DURATION; import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_DURATION;
@ -426,6 +429,12 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
mDeviceSupport.onSetConstantVibration(intensity); mDeviceSupport.onSetConstantVibration(intensity);
break; break;
} }
case ACTION_SET_AUDIO_PROPERTY: {
LOG.info(mDeviceSupport.getDevice().toString());
int volume = intent.getIntExtra(EXTRA_AUDIO_PROPERTY, 0);
mDeviceSupport.onSetAudioProperty(0, volume);
break;
}
case ACTION_CALLSTATE: case ACTION_CALLSTATE:
CallSpec callSpec = new CallSpec(); CallSpec callSpec = new CallSpec();
callSpec.command = intent.getIntExtra(EXTRA_CALL_COMMAND, CallSpec.CALL_UNDEFINED); callSpec.command = intent.getIntExtra(EXTRA_CALL_COMMAND, CallSpec.CALL_UNDEFINED);

View File

@ -38,10 +38,18 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport
import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.hplus.HPlusSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.hplus.HPlusSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.TeclastH30Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.jyou.TeclastH30Support;
import nodomain.freeyourgadget.gadgetbridge.service.devices.here.HereSupport;
import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.GB;
// delte both
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DeviceSupportFactory { public class DeviceSupportFactory {
// delete the following
private static final Logger LOG = LoggerFactory.getLogger(DeviceCommunicationService.class);
private final BluetoothAdapter mBtAdapter; private final BluetoothAdapter mBtAdapter;
private final Context mContext; private final Context mContext;
public DeviceSupportFactory(Context context) { public DeviceSupportFactory(Context context) {
@ -134,6 +142,11 @@ public class DeviceSupportFactory {
case NO1F1: case NO1F1:
deviceSupport = new ServiceDeviceSupport(new No1F1Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); deviceSupport = new ServiceDeviceSupport(new No1F1Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
break; break;
case HERE:
// case HERER:
// case HEREL:
deviceSupport = new ServiceDeviceSupport(new HereSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
break;
case TECLASTH30: case TECLASTH30:
deviceSupport = new ServiceDeviceSupport(new TeclastH30Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); deviceSupport = new ServiceDeviceSupport(new TeclastH30Support(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
break; break;

View File

@ -152,6 +152,11 @@ public class ServiceDeviceSupport implements DeviceSupport {
delegate.onNotification(notificationSpec); delegate.onNotification(notificationSpec);
} }
@Override
public void onSetAudioProperty(int property, int intensity) {
delegate.onSetAudioProperty(property, intensity);
}
@Override @Override
public void onDeleteNotification(int id) { public void onDeleteNotification(int id) {
delegate.onDeleteNotification(id); delegate.onDeleteNotification(id);

View File

@ -0,0 +1,512 @@
/* Copyright (C) 2017 Niclò Balzarotti
This file is part of Gadgetbridge.
Gadgetbridge is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Gadgetbridge is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.here;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import static nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport.BASE_UUID;
import android.net.Uri;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.devices.here.HereConstants;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.StringUtils;
public class HereSupport extends AbstractBTLEDeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(HereSupport.class);
public BluetoothGattCharacteristic batteryCharacteristic = null;
public BluetoothGattCharacteristic infoCharacteristic = null;
public BluetoothGattCharacteristic fwCharacteristic = null;
public BluetoothGattCharacteristic volumeCharacteristic = null;
private final GBDeviceEventVersionInfo versionCmd = new GBDeviceEventVersionInfo();
private final GBDeviceEventBatteryInfo batteryCmd = new GBDeviceEventBatteryInfo();
public HereSupport() {
super(LOG);
addSupportedService(HereConstants.UUID_BATTERY_VALUE);
addSupportedService(HereConstants.UUID_CHARACTERISTIC_INFO);
addSupportedService(HereConstants.UUID_AUDIO_SETTINGS);
}
@Override
protected TransactionBuilder initializeDevice(TransactionBuilder builder) {
LOG.info("Initializing");
gbDevice.setState(GBDevice.State.INITIALIZING);
gbDevice.sendDeviceUpdateIntent(getContext());
batteryCharacteristic = getCharacteristic(HereConstants.UUID_CHARACTERISTIC_BATTERY);
infoCharacteristic = getCharacteristic(HereConstants.UUID_CHARACTERISTIC_INFO);
fwCharacteristic = getCharacteristic(HereConstants.UUID_CHARACTERISTIC_INFO);
volumeCharacteristic = getCharacteristic(HereConstants.UUID_VOLUME);
builder.setGattCallback(this);
builder.notify(batteryCharacteristic, true);
builder.read(fwCharacteristic);
syncSettings(builder);
// fw = builder.read(infoCharacteristic);
gbDevice.setState(GBDevice.State.INITIALIZED);
gbDevice.sendDeviceUpdateIntent(getContext());
LOG.info("Initialization Done");
return builder;
}
@Override
public boolean onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
UUID characteristicUUID = characteristic.getUuid();
byte[] data = characteristic.getValue();
if (data.length == 0)
return true;
if (HereConstants.UUID_CHARACTERISTIC_BATTERY.equals(characteristicUUID)) {
LOG.info("Battery is: " + String.format("%s", data[0]));
batteryCmd.level = ((short) data[0]);
handleGBDeviceEvent(batteryCmd);
return true;
} else if (HereConstants.UUID_FW_VERSION.equals(characteristicUUID)) {
LOG.info("Device info: " + (short)data[0]);
LOG.info("Device info: " + data);
versionCmd.fwVersion = String.format("%s", data);
handleGBDeviceEvent(versionCmd);
} else {
return true;
}
return true;
}
private void syncDateAndTime(TransactionBuilder builder) {
// Calendar cal = Calendar.getInstance();
// String strYear = String.valueOf(cal.get(Calendar.YEAR));
// byte year1 = (byte)Integer.parseInt(strYear.substring(0, 2));
// byte year2 = (byte)Integer.parseInt(strYear.substring(2, 4));
// byte month = (byte)cal.get(Calendar.MONTH);
// byte day = (byte)cal.get(Calendar.DAY_OF_MONTH);
// byte hour = (byte)cal.get(Calendar.HOUR_OF_DAY);
// byte minute = (byte)cal.get(Calendar.MINUTE);
// byte second = (byte)cal.get(Calendar.SECOND);
// byte weekDay = (byte)cal.get(Calendar.DAY_OF_WEEK);
// builder.write(ctrlCharacteristic, commandWithChecksum(
// HereConstants.CMD_SET_DATE_AND_TIME,
// (year1 << 24) | (year2 << 16) | (month << 8) | day,
// (hour << 24) | (minute << 16) | (second << 8) | weekDay
// ));
}
private void syncSettings(TransactionBuilder builder) {
// syncDateAndTime(builder);
// // TODO: unhardcode and separate stuff
// builder.write(ctrlCharacteristic, commandWithChecksum(
// HereConstants.CMD_SET_HEARTRATE_WARNING_VALUE, 0, 152
// ));
// builder.write(ctrlCharacteristic, commandWithChecksum(
// HereConstants.CMD_SET_TARGET_STEPS, 0, 10000
// ));
// builder.write(ctrlCharacteristic, commandWithChecksum(
// HereConstants.CMD_GET_STEP_COUNT, 0, 0
// ));
// builder.write(ctrlCharacteristic, commandWithChecksum(
// HereConstants.CMD_GET_SLEEP_TIME, 0, 0
// ));
// builder.write(ctrlCharacteristic, commandWithChecksum(
// HereConstants.CMD_SET_NOON_TIME, 12 * 60 * 60, 14 * 60 * 60 // 12:00 - 14:00
// ));
// builder.write(ctrlCharacteristic, commandWithChecksum(
// HereConstants.CMD_SET_SLEEP_TIME, 21 * 60 * 60, 8 * 60 * 60 // 21:00 - 08:00
// ));
// builder.write(ctrlCharacteristic, commandWithChecksum(
// HereConstants.CMD_SET_INACTIVITY_WARNING_TIME, 0, 0
// ));
// // do not disturb and a couple more features
// byte dndStartHour = 22;
// byte dndStartMin = 0;
// byte dndEndHour = 8;
// byte dndEndMin = 0;
// boolean dndToggle = false;
// boolean vibrationToggle = true;
// boolean wakeOnRaiseToggle = true;
// builder.write(ctrlCharacteristic, commandWithChecksum(
// HereConstants.CMD_SET_DND_SETTINGS,
// (dndStartHour << 24) | (dndStartMin << 16) | (dndEndHour << 8) | dndEndMin,
// ((dndToggle ? 0 : 1) << 2) | ((vibrationToggle ? 1 : 0) << 1) | (wakeOnRaiseToggle ? 1 : 0)
// ));
}
private void showNotification(byte icon, String title, String message) {
// try {
// TransactionBuilder builder = performInitialized("ShowNotification");
// byte[] titleBytes = stringToUTF8Bytes(title, 16);
// byte[] messageBytes = stringToUTF8Bytes(message, 80);
// for (int i = 1; i <= 7; i++)
// {
// byte[] currentPacket = new byte[20];
// currentPacket[0] = HereConstants.CMD_ACTION_SHOW_NOTIFICATION;
// currentPacket[1] = 7;
// currentPacket[2] = (byte)i;
// switch(i) {
// case 1:
// currentPacket[4] = icon;
// break;
// case 2:
// if (titleBytes != null) {
// System.arraycopy(titleBytes, 0, currentPacket, 3, 6);
// System.arraycopy(titleBytes, 6, currentPacket, 10, 10);
// }
// break;
// default:
// if (messageBytes != null) {
// System.arraycopy(messageBytes, 16 * (i - 3), currentPacket, 3, 6);
// System.arraycopy(messageBytes, 6 + 16 * (i - 3), currentPacket, 10, 10);
// }
// break;
// }
// builder.write(ctrlCharacteristic, currentPacket);
// }
// performConnected(builder.getTransaction());
// } catch (IOException e) {
// LOG.warn(e.getMessage());
// }
}
@Override
public boolean useAutoConnect() {
return true;
}
@Override
public void onNotification(NotificationSpec notificationSpec) {
// String notificationTitle = StringUtils.getFirstOf(notificationSpec.sender, notificationSpec.title);
// byte icon;
// switch (notificationSpec.type) {
// case GENERIC_SMS:
// icon = HereConstants.ICON_SMS;
// break;
// case FACEBOOK:
// case FACEBOOK_MESSENGER:
// icon = HereConstants.ICON_FACEBOOK;
// break;
// case TWITTER:
// icon = HereConstants.ICON_TWITTER;
// break;
// case WHATSAPP:
// icon = HereConstants.ICON_WHATSAPP;
// break;
// default:
// icon = HereConstants.ICON_LINE;
// break;
// }
// showNotification(icon, notificationTitle, notificationSpec.body);
}
@Override
public void onDeleteNotification(int id) {
}
@Override
public void onSetAlarms(ArrayList<? extends Alarm> alarms) {
// try {
// TransactionBuilder builder = performInitialized("SetAlarms");
// for (int i = 0; i < alarms.size(); i++)
// {
// byte cmd;
// switch (i) {
// case 0:
// cmd = HereConstants.CMD_SET_ALARM_1;
// break;
// case 1:
// cmd = HereConstants.CMD_SET_ALARM_2;
// break;
// case 2:
// cmd = HereConstants.CMD_SET_ALARM_3;
// break;
// default:
// return;
// }
// Calendar cal = alarms.get(i).getAlarmCal();
// builder.write(ctrlCharacteristic, commandWithChecksum(
// cmd,
// alarms.get(i).isEnabled() ? cal.get(Calendar.HOUR_OF_DAY) : -1,
// alarms.get(i).isEnabled() ? cal.get(Calendar.MINUTE) : -1
// ));
// }
// performConnected(builder.getTransaction());
// GB.toast(getContext(), "Alarm settings applied - do note that the current device does not support day specification", Toast.LENGTH_LONG, GB.INFO);
// } catch(IOException e) {
// LOG.warn(e.getMessage());
// }
}
@Override
public void onSetTime() {
// try {
// TransactionBuilder builder = performInitialized("SetTime");
// syncDateAndTime(builder);
// performConnected(builder.getTransaction());
// } catch(IOException e) {
// LOG.warn(e.getMessage());
// }
}
@Override
public void onSetCallState(CallSpec callSpec) {
// switch (callSpec.command) {
// case CallSpec.CALL_INCOMING:
// showNotification(HereConstants.ICON_CALL, callSpec.name, callSpec.number);
// break;
// }
}
@Override
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
}
@Override
public void onSetAudioProperty(int property, int volume) {
TransactionBuilder builder = new TransactionBuilder("volume");
LOG.info("setting audio");
switch (property) {
case 0: // volume
LOG.info("setting volume to "+volume);
builder.write(volumeCharacteristic, new byte[]{(byte) volume});
builder.queue(getQueue());
break;
}
}
@Override
public void onSetMusicState(MusicStateSpec stateSpec) {
}
@Override
public void onSetMusicInfo(MusicSpec musicSpec) {
}
@Override
public void onEnableRealtimeSteps(boolean enable) {
onEnableRealtimeHeartRateMeasurement(enable);
}
@Override
public void onInstallApp(Uri uri) {
}
@Override
public void onAppInfoReq() {
}
@Override
public void onAppStart(UUID uuid, boolean start) {
}
@Override
public void onAppDelete(UUID uuid) {
}
@Override
public void onAppConfiguration(UUID appUuid, String config) {
}
@Override
public void onAppReorder(UUID[] uuids) {
}
@Override
public void onFetchActivityData() {
}
@Override
public void onReboot() {
// try {
// TransactionBuilder builder = performInitialized("Reboot");
// builder.write(ctrlCharacteristic, commandWithChecksum(
// HereConstants.CMD_ACTION_REBOOT_DEVICE, 0, 0
// ));
// performConnected(builder.getTransaction());
// } catch(Exception e) {
// LOG.warn(e.getMessage());
// }
}
@Override
public void onHeartRateTest() {
// try {
// TransactionBuilder builder = performInitialized("HeartRateTest");
// builder.write(ctrlCharacteristic, commandWithChecksum(
// HereConstants.CMD_ACTION_HEARTRATE_SWITCH, 0, 1
// ));
// performConnected(builder.getTransaction());
// } catch(Exception e) {
// LOG.warn(e.getMessage());
// }
}
@Override
public void onEnableRealtimeHeartRateMeasurement(boolean enable) {
// // TODO: test
// try {
// TransactionBuilder builder = performInitialized("RealTimeHeartMeasurement");
// builder.write(ctrlCharacteristic, commandWithChecksum(
// HereConstants.CMD_SET_HEARTRATE_AUTO, 0, enable ? 1 : 0
// ));
// performConnected(builder.getTransaction());
// } catch(Exception e) {
// LOG.warn(e.getMessage());
// }
}
@Override
public void onFindDevice(boolean start) {
}
@Override
public void onSetConstantVibration(int integer) {
}
@Override
public void onScreenshotReq() {
}
@Override
public void onEnableHeartRateSleepSupport(boolean enable) {
}
@Override
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
}
@Override
public void onDeleteCalendarEvent(byte type, long id) {
}
@Override
public void onSendConfiguration(String config) {
}
@Override
public void onTestNewFunction() {
}
@Override
public void onSendWeather(WeatherSpec weatherSpec) {
}
// private byte[] commandWithChecksum(byte cmd, int argSlot1, int argSlot2)
// {
// // ByteBuffer buf = ByteBuffer.allocate(10);
// // buf.put(cmd);
// // buf.putInt(argSlot1);
// // buf.putInt(argSlot2);
// // byte[] bytesToWrite = buf.array();
// // byte checksum = 0;
// // for (byte b : bytesToWrite) {
// // checksum += b;
// // }
// // bytesToWrite[9] = checksum;
// ByteBuffer buf = ByteBuffer.allocate(10);
// return buf;
// }
private byte[] stringToUTF8Bytes(String src, int byteCount) {
// try {
// if (src == null)
// return null;
// for (int i = src.length(); i > 0; i--) {
// String sub = src.substring(0, i);
// byte[] subUTF8 = sub.getBytes("UTF-8");
// if (subUTF8.length == byteCount) {
// return subUTF8;
// }
// if (subUTF8.length < byteCount) {
// byte[] largerSubUTF8 = new byte[byteCount];
// System.arraycopy(subUTF8, 0, largerSubUTF8, 0, subUTF8.length);
// return largerSubUTF8;
// }
// }
// } catch (UnsupportedEncodingException e) {
// LOG.warn(e.getMessage());
// }
return null;
}
}

View File

@ -431,6 +431,11 @@ public class HPlusSupport extends AbstractBTLEDeviceSupport {
} }
@Override
public void onSetAudioProperty(int property, int volume) {
}
@Override @Override
public void onSetTime() { public void onSetTime() {

View File

@ -316,6 +316,12 @@ public class TeclastH30Support extends AbstractBTLEDeviceSupport {
} }
@Override
public void onSetAudioProperty(int property, int volume) {
}
@Override @Override
public void onSetMusicState(MusicStateSpec stateSpec) { public void onSetMusicState(MusicStateSpec stateSpec) {

View File

@ -105,6 +105,10 @@ public class LiveviewSupport extends AbstractSerialDeviceSupport {
//nothing to do ATM //nothing to do ATM
} }
@Override
public void onSetAudioProperty(int property, int volume) {
}
@Override @Override
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) { public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
//nothing to do ATM //nothing to do ATM

View File

@ -452,6 +452,11 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
// not supported // not supported
} }
@Override
public void onSetAudioProperty(int property, int volume) {
}
/** /**
* Part of device initialization process. Do not call manually. * Part of device initialization process. Do not call manually.
* *

View File

@ -663,6 +663,12 @@ public class MiBand2Support extends AbstractBTLEDeviceSupport {
public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) { public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) {
} }
@Override
public void onSetAudioProperty(int property, int volume) {
}
private boolean isAlarmClockRinging() { private boolean isAlarmClockRinging() {
// don't synchronize, this is not really important // don't synchronize, this is not really important
return alarmClockRinging; return alarmClockRinging;

View File

@ -355,6 +355,11 @@ public class No1F1Support extends AbstractBTLEDeviceSupport {
} }
@Override
public void onSetAudioProperty(int property, int volume) {
}
@Override @Override
public void onDeleteCalendarEvent(byte type, long id) { public void onDeleteCalendarEvent(byte type, long id) {

View File

@ -127,6 +127,11 @@ public class PebbleSupport extends AbstractSerialDeviceSupport {
} }
@Override
public void onSetAudioProperty(int property, int volume) {
}
@Override @Override
public void onSetConstantVibration(int intensity) { public void onSetConstantVibration(int intensity) {

View File

@ -185,6 +185,11 @@ public class VibratissimoSupport extends AbstractBTLEDeviceSupport {
} }
@Override
public void onSetAudioProperty(int property, int volume) {
}
@Override @Override
public void onAppStart(UUID uuid, boolean start) { public void onAppStart(UUID uuid, boolean start) {

View File

@ -51,6 +51,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.no1f1.No1F1Coordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.vibratissimo.VibratissimoCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.vibratissimo.VibratissimoCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.here.HereCoordinator;
import nodomain.freeyourgadget.gadgetbridge.entities.Device; import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributes; import nodomain.freeyourgadget.gadgetbridge.entities.DeviceAttributes;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
@ -198,6 +199,7 @@ public class DeviceHelper {
result.add(new No1F1Coordinator()); result.add(new No1F1Coordinator());
result.add(new MakibesF68Coordinator()); result.add(new MakibesF68Coordinator());
result.add(new EXRIZUK8Coordinator()); result.add(new EXRIZUK8Coordinator());
result.add(new HereCoordinator());
result.add(new TeclastH30Coordinator()); result.add(new TeclastH30Coordinator());
return result; return result;

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="false"
android:layout_centerInParent="false"
android:layout_centerVertical="false"
android:weightSum="1">
<SeekBar
android:id="@+id/volume_seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:max="28"
android:scaleY="4.0" />
</LinearLayout>

View File

@ -231,12 +231,27 @@
card_view:srcCompat="@drawable/ic_activity_graphs" /> card_view:srcCompat="@drawable/ic_activity_graphs" />
<ImageView <ImageView
android:id="@+id/device_action_find" android:id="@+id/device_action_show_audio_settings"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_below="@id/device_image" android:layout_below="@id/device_image"
android:layout_margin="4dp" android:layout_margin="4dp"
android:layout_toEndOf="@id/device_action_show_activity_graphs" android:layout_toEndOf="@id/device_action_show_activity_graphs"
android:background="?android:attr/selectableItemBackground"
android:clickable="true"
android:contentDescription="@string/controlcenter_start_audiosetting"
android:padding="4dp"
android:scaleType="fitXY"
android:tint="@color/secondarytext"
card_view:srcCompat="@drawable/ic_settings" />
<ImageView
android:id="@+id/device_action_find"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_below="@id/device_image"
android:layout_margin="4dp"
android:layout_toEndOf="@id/device_action_show_audio_settings"
android:clickable="true" android:clickable="true"
android:contentDescription="@string/controlcenter_find_device" android:contentDescription="@string/controlcenter_find_device"
android:padding="4dp" android:padding="4dp"

View File

@ -287,6 +287,7 @@
<string name="miband_prefs_fitness_goal">Target steps for each day</string> <string name="miband_prefs_fitness_goal">Target steps for each day</string>
<string name="dbaccess_error_executing">Error executing \'%1$s\'</string> <string name="dbaccess_error_executing">Error executing \'%1$s\'</string>
<string name="controlcenter_start_activitymonitor">Your activity (ALPHA)</string> <string name="controlcenter_start_activitymonitor">Your activity (ALPHA)</string>
<string name="controlcenter_start_audiosetting">Audio Settings</string>
<string name="cannot_connect">Cannot connect: %1$s</string> <string name="cannot_connect">Cannot connect: %1$s</string>
<string name="installer_activity_unable_to_find_handler">Unable to find a handler to install this file.</string> <string name="installer_activity_unable_to_find_handler">Unable to find a handler to install this file.</string>
<string name="pbw_install_handler_unable_to_install">Unable to install the given file: %1$s</string> <string name="pbw_install_handler_unable_to_install">Unable to install the given file: %1$s</string>
@ -474,4 +475,5 @@
<string name="_pebble_watch_open_on_phone">Open on phone</string> <string name="_pebble_watch_open_on_phone">Open on phone</string>
<string name="_pebble_watch_mute">Mute</string> <string name="_pebble_watch_mute">Mute</string>
<string name="_pebble_watch_reply">Reply</string> <string name="_pebble_watch_reply">Reply</string>
<string name="title_audio_activity">Audio Settings</string>
</resources> </resources>