mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Even Realities G1: Add Settings menu and silent mode toggle
This change adds in a toggle for "silent mode" which turns the displays off and makes the glasses act like normal glasses. This change also adds the following configuration settings: - Screen Location/height - Screen Depth Effect - Head Tilt Activation Angle - Toggle Auto Brightness - Brightness Level - Wear Detection Also refactored Communications file to have a consistent naming style.
This commit is contained in:
+3
@@ -625,4 +625,7 @@ public class DeviceSettingsPreferenceConst {
|
||||
public static final String PREF_DISPLAY_ON_START = "display_on_start";
|
||||
public static final String PREF_DISPLAY_ON_END = "display_on_end";
|
||||
public static final String PREF_CONNECTION_PRIORITY_LOW_POWER = "connection_priority_low_power";
|
||||
public static final String PREF_EVEN_REALITIES_SCREEN_HEIGHT = "pref_even_realities_g1_screen_height";
|
||||
public static final String PREF_EVEN_REALITIES_SCREEN_DEPTH = "pref_even_realities_g1_screen_depth";
|
||||
public static final String PREF_EVEN_REALITIES_SCREEN_ACTIVATION_ANGLE = "pref_even_realities_g1_screen_activation_angle";
|
||||
}
|
||||
|
||||
+4
@@ -622,6 +622,10 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
|
||||
addPreferenceHandlerFor(PREF_WEAR_SENSOR_TOGGLE);
|
||||
addPreferenceHandlerFor(PREF_BANDW_PSERIES_GUI_VPT_LEVEL);
|
||||
|
||||
addPreferenceHandlerFor(PREF_EVEN_REALITIES_SCREEN_HEIGHT);
|
||||
addPreferenceHandlerFor(PREF_EVEN_REALITIES_SCREEN_DEPTH);
|
||||
addPreferenceHandlerFor(PREF_EVEN_REALITIES_SCREEN_ACTIVATION_ANGLE);
|
||||
|
||||
addPreferenceHandlerFor(PREF_HYBRID_HR_DRAW_WIDGET_CIRCLES);
|
||||
addPreferenceHandlerFor(PREF_HYBRID_HR_FORCE_WHITE_COLOR);
|
||||
addPreferenceHandlerFor(PREF_HYBRID_HR_SAVE_RAW_ACTIVITY_FILES);
|
||||
|
||||
+37
@@ -1,6 +1,8 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.evenrealities;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
@@ -9,12 +11,15 @@ import androidx.annotation.NonNull;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBException;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCardAction;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
|
||||
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
@@ -174,4 +179,36 @@ public class G1DeviceCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeviceCardAction> getCustomActions() {
|
||||
return Collections.singletonList(new DeviceCardAction() {
|
||||
@Override
|
||||
public int getIcon(GBDevice device) {
|
||||
return R.drawable.ic_dnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription(final GBDevice device, final Context context) {
|
||||
return context.getString(R.string.silent_mode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(final GBDevice device, final Context context) {
|
||||
final Intent intent = new Intent(G1Constants.INTENT_TOGGLE_SILENT_MODE);
|
||||
intent.putExtra(GBDevice.EXTRA_DEVICE, device);
|
||||
intent.setPackage(context.getPackageName());
|
||||
context.sendBroadcast(intent);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
|
||||
if (device.isConnected()) {
|
||||
return new int[]{R.xml.devicesettings_even_realities_g1_display};
|
||||
} else {
|
||||
return new int[]{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+334
-27
@@ -14,7 +14,7 @@ public class G1Communications {
|
||||
public abstract static class CommandHandler {
|
||||
private final boolean expectResponse;
|
||||
private final Function<byte[], Boolean> callback;
|
||||
protected short sequence;
|
||||
protected byte sequence;
|
||||
private byte[] responsePayload;
|
||||
private int retryCount;
|
||||
|
||||
@@ -26,7 +26,7 @@ public class G1Communications {
|
||||
}
|
||||
|
||||
public boolean needsGlobalSequence() { return false; }
|
||||
public void setGlobalSequence(short sequence) {
|
||||
public void setGlobalSequence(byte sequence) {
|
||||
this.sequence = sequence;
|
||||
}
|
||||
public int getTimeout() {
|
||||
@@ -95,8 +95,29 @@ public class G1Communications {
|
||||
public abstract String getName();
|
||||
}
|
||||
|
||||
public static class CommandFirmwareInfo extends CommandHandler {
|
||||
public CommandFirmwareInfo(Function<byte[], Boolean> callback) {
|
||||
public static class CommandSendInit extends CommandHandler {
|
||||
public CommandSendInit() {
|
||||
super(true, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
return new byte[] { G1Constants.CommandId.INIT.id, (byte)0xFB };
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean responseMatches(byte[] payload) {
|
||||
return payload[0] == G1Constants.CommandId.INIT.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "send_init";
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandGetFirmwareInfo extends CommandHandler {
|
||||
public CommandGetFirmwareInfo(Function<byte[], Boolean> callback) {
|
||||
super(true, callback);
|
||||
}
|
||||
|
||||
@@ -115,12 +136,12 @@ public class G1Communications {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "firmware_info";
|
||||
return "get_firmware_info";
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandBatteryLevel extends CommandHandler {
|
||||
public CommandBatteryLevel(Function<byte[], Boolean> callback) {
|
||||
public static class CommandGetBatteryInfo extends CommandHandler {
|
||||
public CommandGetBatteryInfo(Function<byte[], Boolean> callback) {
|
||||
super(true, callback);
|
||||
}
|
||||
@Override
|
||||
@@ -138,14 +159,14 @@ public class G1Communications {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "battery_level";
|
||||
return "get_battery_info";
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandHeartBeat extends CommandHandler {
|
||||
public CommandHeartBeat(short sequence) {
|
||||
public static class CommandSendHeartBeat extends CommandHandler {
|
||||
public CommandSendHeartBeat(byte sequence) {
|
||||
super(false, null);
|
||||
super.sequence = sequence;
|
||||
setGlobalSequence(sequence);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -154,11 +175,9 @@ public class G1Communications {
|
||||
G1Constants.CommandId.HEARTBEAT.id,
|
||||
0x00, // length is a short
|
||||
0x06, // length
|
||||
// TODO: What the heck is the 0x04 and why is the sequence split?
|
||||
// Need to look at a real capture for this.
|
||||
(byte) (sequence % 0xFF),
|
||||
0x04,
|
||||
(byte) (sequence % 0xFF)
|
||||
sequence, // Sequence is included twice for some reason, also verified by the FW.
|
||||
0x04, // Magic value that the FW looks for.
|
||||
sequence
|
||||
};
|
||||
}
|
||||
|
||||
@@ -169,18 +188,18 @@ public class G1Communications {
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "heart_beat";
|
||||
return "send_heart_beat";
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandTimeAndWeather extends CommandHandler {
|
||||
public static class CommandSetTimeAndWeather extends CommandHandler {
|
||||
long timeMilliseconds;
|
||||
boolean use12HourFormat;
|
||||
byte tempInCelsius;
|
||||
byte weatherIcon;
|
||||
boolean useFahrenheit;
|
||||
|
||||
public CommandTimeAndWeather(long timeMilliseconds, boolean use12HourFormat, WeatherSpec weatherInfo, boolean useFahrenheit) {
|
||||
public CommandSetTimeAndWeather(long timeMilliseconds, boolean use12HourFormat, WeatherSpec weatherInfo, boolean useFahrenheit) {
|
||||
super(true, null);
|
||||
this.timeMilliseconds = timeMilliseconds;
|
||||
this.use12HourFormat = use12HourFormat;
|
||||
@@ -194,15 +213,20 @@ public class G1Communications {
|
||||
}
|
||||
this.useFahrenheit = useFahrenheit;
|
||||
}
|
||||
public CommandSetTimeAndWeather(long timeMilliseconds, boolean use12HourFormat, boolean useFahrenheit) {
|
||||
this(timeMilliseconds, use12HourFormat, null, useFahrenheit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsGlobalSequence() { return true; }
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
byte[] packet = new byte[] {
|
||||
G1Constants.CommandId.DASHBOARD_CONFIG.id,
|
||||
G1Constants.DashboardConfigSubCommand.SET_TIME_AND_WEATHER.id,
|
||||
// Sequence place holder
|
||||
0x00,
|
||||
0x00,
|
||||
sequence,
|
||||
// Magic number?
|
||||
0x01,
|
||||
// Time 32bit place holders
|
||||
@@ -227,7 +251,6 @@ public class G1Communications {
|
||||
// 24H/12H
|
||||
(byte)(use12HourFormat ? 0x01 : 0x00)
|
||||
};
|
||||
BLETypeConversions.writeUint16BE(packet, 2, sequence);
|
||||
BLETypeConversions.writeUint32(packet, 5, (int)(timeMilliseconds / 1000));
|
||||
BLETypeConversions.writeUint64(packet, 9, timeMilliseconds);
|
||||
|
||||
@@ -241,16 +264,300 @@ public class G1Communications {
|
||||
}
|
||||
|
||||
// Command should match and the sequence should match.
|
||||
// TODO actually check the sequence (need to confirm the offset).
|
||||
return payload[0] == G1Constants.CommandId.DASHBOARD_CONFIG.id &&
|
||||
payload[1] == G1Constants.DashboardConfigSubCommand.SET_TIME_AND_WEATHER.id;
|
||||
// payload[2] == (byte)(sequence >> 8) &&
|
||||
// payload[3] == (byte)sequence;
|
||||
payload[1] == G1Constants.DashboardConfigSubCommand.SET_TIME_AND_WEATHER.id &&
|
||||
payload[3] == sequence;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "time_and_weather";
|
||||
return "set_time_and_weather";
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandGetSilentModeSettings extends CommandHandler {
|
||||
public CommandGetSilentModeSettings(Function<byte[], Boolean> callback) {
|
||||
super(true, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
return new byte[] { G1Constants.CommandId.GET_SILENT_MODE_SETTINGS.id };
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean responseMatches(byte[] payload) {
|
||||
return payload.length >= 4 && payload[0] == G1Constants.CommandId.GET_SILENT_MODE_SETTINGS.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "get_silent_status";
|
||||
}
|
||||
|
||||
public static boolean isEnabled(byte[] payload) {
|
||||
return payload[2] == G1Constants.SilentStatus.ENABLE;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandSetSilentModeSettings extends CommandHandler {
|
||||
private final boolean enable;
|
||||
public CommandSetSilentModeSettings(boolean enable) {
|
||||
super(true, null);
|
||||
this.enable = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
return new byte[] {
|
||||
G1Constants.CommandId.SET_SILENT_MODE_SETTINGS.id,
|
||||
(byte)(enable ? G1Constants.SilentStatus.ENABLE : G1Constants.SilentStatus.DISABLE),
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean responseMatches(byte[] payload) {
|
||||
return payload.length > 1 && payload[0] == G1Constants.CommandId.SET_SILENT_MODE_SETTINGS.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "set_silent_mode_settings_" + (enable ? "enabled" : "disabled");
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandGetDisplaySettings extends CommandHandler {
|
||||
public CommandGetDisplaySettings(Function<byte[], Boolean> callback) {
|
||||
super(true, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
return new byte[] { G1Constants.CommandId.GET_DISPLAY_SETTINGS.id };
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean responseMatches(byte[] payload) {
|
||||
return payload.length >= 4 && payload[0] == G1Constants.CommandId.GET_DISPLAY_SETTINGS.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "get_display_settings";
|
||||
}
|
||||
|
||||
public static byte getHeight(byte[] payload) {
|
||||
return payload[2];
|
||||
}
|
||||
|
||||
public static byte getDepth(byte[] payload) {
|
||||
return payload[3];
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandSetDisplaySettings extends CommandHandler {
|
||||
private final boolean preview;
|
||||
private final byte height;
|
||||
private final byte depth;
|
||||
public CommandSetDisplaySettings(boolean preview, byte height, byte depth) {
|
||||
super(true, null);
|
||||
this.preview = preview;
|
||||
this.height = height;
|
||||
this.depth = depth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsGlobalSequence() { return true; }
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
return new byte[] {
|
||||
G1Constants.CommandId.SET_DISPLAY_SETTINGS.id,
|
||||
0x08, // Subcommand?
|
||||
0x00,
|
||||
sequence,
|
||||
0x02, // Seems to be a magic number?
|
||||
preview ? 0x01 : (byte)0x00,
|
||||
height,
|
||||
depth
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean responseMatches(byte[] payload) {
|
||||
return payload.length >= 6 &&
|
||||
payload[0] == G1Constants.CommandId.SET_DISPLAY_SETTINGS.id &&
|
||||
payload[1] == 0x06 && // Magic Number
|
||||
payload[3] == sequence;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "set_display_settings_" + height + "_" + depth;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandGetHeadGestureSettings extends CommandHandler {
|
||||
public CommandGetHeadGestureSettings(Function<byte[], Boolean> callback) {
|
||||
super(true, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
return new byte[] { G1Constants.CommandId.GET_HEAD_GESTURE_SETTINGS.id };
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean responseMatches(byte[] payload) {
|
||||
return payload.length >= 4 && payload[0] == G1Constants.CommandId.GET_HEAD_GESTURE_SETTINGS.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "get_head_gesture_settings";
|
||||
}
|
||||
|
||||
public static byte getActivationAngle(byte[] payload) {
|
||||
return payload[2];
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandSetHeadGestureSettings extends CommandHandler {
|
||||
private final byte angle;
|
||||
// Allowed Angles are 0-60.
|
||||
public CommandSetHeadGestureSettings(byte angle) {
|
||||
super(true, null);
|
||||
this.angle = angle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
return new byte[] {
|
||||
G1Constants.CommandId.SET_HEAD_GESTURE_SETTINGS.id,
|
||||
angle,
|
||||
// Magic number, other project called it the "level setting".
|
||||
// Maybe try sending 0x00 and see what happens?
|
||||
0x01
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean responseMatches(byte[] payload) {
|
||||
return payload.length >= 1 && payload[0] == G1Constants.CommandId.SET_HEAD_GESTURE_SETTINGS.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "set_head_gesture_settings_" + angle;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandGetBrightnessSettings extends CommandHandler {
|
||||
public CommandGetBrightnessSettings(Function<byte[], Boolean> callback) {
|
||||
super(true, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
return new byte[] { G1Constants.CommandId.GET_BRIGHTNESS_SETTINGS.id };
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean responseMatches(byte[] payload) {
|
||||
return payload.length >= 3 && payload[0] == G1Constants.CommandId.GET_BRIGHTNESS_SETTINGS.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "get_brightness_settings";
|
||||
}
|
||||
|
||||
public static byte getBrightnessLevel(byte[] payload) {
|
||||
return payload[2];
|
||||
}
|
||||
|
||||
public static boolean isAutoBrightnessEnabled(byte[] payload) {
|
||||
return payload[3] == 0x01;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandSetBrightnessSettings extends CommandHandler {
|
||||
private final boolean enableAutoBrightness;
|
||||
private final byte brightnessLevel;
|
||||
public CommandSetBrightnessSettings(boolean enableAutoBrightness, byte brightnessLevel) {
|
||||
super(true, null);
|
||||
this.enableAutoBrightness = enableAutoBrightness;
|
||||
this.brightnessLevel = brightnessLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
return new byte[] {
|
||||
G1Constants.CommandId.SET_BRIGHTNESS_SETTINGS.id,
|
||||
brightnessLevel,
|
||||
enableAutoBrightness ? 0x01 : (byte)0x00
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean responseMatches(byte[] payload) {
|
||||
return payload.length > 1 && payload[0] == G1Constants.CommandId.SET_BRIGHTNESS_SETTINGS.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "set_brightness_settings_" + enableAutoBrightness + "_" + brightnessLevel;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandGetWearDetectionSettings extends CommandHandler {
|
||||
public CommandGetWearDetectionSettings(Function<byte[], Boolean> callback) {
|
||||
super(true, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
return new byte[] { G1Constants.CommandId.GET_WEAR_DETECTION_SETTINGS.id };
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean responseMatches(byte[] payload) {
|
||||
return payload.length >= 2 && payload[0] == G1Constants.CommandId.GET_WEAR_DETECTION_SETTINGS.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "get_wear_detection_settings";
|
||||
}
|
||||
|
||||
public static boolean isEnabled(byte[] payload) {
|
||||
return payload[2] == 0x01;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandSetWearDetectionSettings extends CommandHandler {
|
||||
private final boolean enable;
|
||||
public CommandSetWearDetectionSettings(boolean enable) {
|
||||
super(true, null);
|
||||
this.enable = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] serialize() {
|
||||
return new byte[] {
|
||||
G1Constants.CommandId.SET_WEAR_DETECTION_SETTINGS.id,
|
||||
enable ? 0x01 : (byte)0x00
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean responseMatches(byte[] payload) {
|
||||
return payload.length >= 2 && payload[0] == G1Constants.CommandId.SET_WEAR_DETECTION_SETTINGS.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "set_wear_detection_settings_" + (enable ? "enabled" : "disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+27
-4
@@ -12,7 +12,9 @@ public class G1Constants {
|
||||
public static final int MTU = 251;
|
||||
public static final int HEART_BEAT_DELAY_MS = 28000;
|
||||
public static final int DEFAULT_COMMAND_TIMEOUT_MS = 5000;
|
||||
public static final int DISPLAY_SETTINGS_PREVIEW_DELAY = 3000;
|
||||
public static final int DEFAULT_RETRY_COUNT = 5;
|
||||
public static final String INTENT_TOGGLE_SILENT_MODE = "nodomain.freeyourgadget.gadgetbridge.evenrealities.silent_mode";
|
||||
|
||||
// Extract the L or R at the end of the device prefix.
|
||||
public static Side getSideFromFullName(String deviceName) {
|
||||
@@ -71,12 +73,15 @@ public class G1Constants {
|
||||
}
|
||||
}
|
||||
|
||||
public static class CommandStatus {
|
||||
public static final byte FAILED = (byte)0xCA;
|
||||
public static final byte DATA_CONTINUES = (byte)0xCA;
|
||||
public static final byte SUCCESS = (byte)0xC9;
|
||||
}
|
||||
|
||||
// TODO: Lifted these from a different project, some of them are wrong.
|
||||
public enum CommandId {
|
||||
SILENT_MODE((byte) 0x03),
|
||||
NOTIFICATION_CONFIG((byte) 0x04),
|
||||
|
||||
DASHBOARD_CONFIG((byte) 0x06),
|
||||
DASHBOARD((byte) 0x22),
|
||||
FW_INFO_REQUEST((byte) 0x23),
|
||||
@@ -84,7 +89,18 @@ public class G1Constants {
|
||||
BATTERY_LEVEL((byte) 0x2C),
|
||||
INIT((byte) 0x4D),
|
||||
NOTIFICATION((byte) 0x4B),
|
||||
FW_INFO_RESPONSE((byte) 0x6E);
|
||||
FW_INFO_RESPONSE((byte) 0x6E),
|
||||
DEVICE_ACTION((byte) 0xF5),
|
||||
GET_SILENT_MODE_SETTINGS((byte) 0x2B), // There is more info in this one
|
||||
SET_SILENT_MODE_SETTINGS((byte) 0x03),
|
||||
GET_DISPLAY_SETTINGS((byte) 0x3B),
|
||||
SET_DISPLAY_SETTINGS((byte) 0x26),
|
||||
GET_HEAD_GESTURE_SETTINGS((byte) 0x32),
|
||||
SET_HEAD_GESTURE_SETTINGS((byte) 0x0B),
|
||||
GET_BRIGHTNESS_SETTINGS((byte) 0x29),
|
||||
SET_BRIGHTNESS_SETTINGS((byte) 0x01),
|
||||
GET_WEAR_DETECTION_SETTINGS((byte) 0x3A),
|
||||
SET_WEAR_DETECTION_SETTINGS((byte) 0x27);
|
||||
|
||||
final public byte id;
|
||||
|
||||
@@ -96,7 +112,9 @@ public class G1Constants {
|
||||
public enum DashboardConfigSubCommand {
|
||||
SET_MODE((byte) 0x07),
|
||||
UNKNOWN_1((byte) 0x0C),
|
||||
SET_TIME_AND_WEATHER((byte) 0x15);
|
||||
SET_TIME_AND_WEATHER((byte) 0x15),
|
||||
// Not sure why they use this one sometimes.
|
||||
SET_TIME_AND_WEATHER_ALSO((byte) 0x16);
|
||||
|
||||
final public byte id;
|
||||
|
||||
@@ -104,4 +122,9 @@ public class G1Constants {
|
||||
this.id = id;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SilentStatus {
|
||||
public static final byte ENABLE = 0x0C;
|
||||
public static final byte DISABLE = 0x0A;
|
||||
}
|
||||
}
|
||||
+95
-33
@@ -3,13 +3,18 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.evenrealities;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.icu.util.Calendar;
|
||||
import android.icu.util.TimeZone;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -43,10 +48,10 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(G1DeviceSupport.class);
|
||||
private final Handler backgroundTasksHandler = new Handler(Looper.getMainLooper());
|
||||
private G1SideManager leftSide;
|
||||
private G1SideManager rightSide;
|
||||
private BroadcastReceiver intentReceiver = null;
|
||||
private final Object sendTimeLock = new Object();
|
||||
|
||||
private G1SideManager leftSide = null;
|
||||
private G1SideManager rightSide = null;
|
||||
public G1DeviceSupport() {
|
||||
this(LOG);
|
||||
}
|
||||
@@ -97,6 +102,16 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
// device before the devices are linked.
|
||||
super.setContext(device, btAdapter, context);
|
||||
}
|
||||
|
||||
// Register to receive silent mode intent calls from the UI.
|
||||
if (intentReceiver == null) {
|
||||
intentReceiver = new IntentReceiver();
|
||||
ContextCompat.registerReceiver(
|
||||
context,
|
||||
intentReceiver,
|
||||
new IntentFilter(G1Constants.INTENT_TOGGLE_SILENT_MODE),
|
||||
ContextCompat.RECEIVER_NOT_EXPORTED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -142,17 +157,21 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
builder.setCallback(this);
|
||||
builder.notify(rx, true);
|
||||
|
||||
// If the manager is initializing, then the whole device is initializing as well.
|
||||
// If the side is in the connected state, it is ready to be initialized.
|
||||
// IMPORTANT: use getDevice(deviceIdx), not getDevice(/* 0 */) here otherwise the device
|
||||
// will lock up in a half initialized state because GB thinks the left side is initialized,
|
||||
// after because the right ran first.
|
||||
if (side.getState() == GBDevice.State.CONNECTED) {
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING,
|
||||
builder.add(new SetDeviceStateAction(getDevice(deviceIdx), GBDevice.State.INITIALIZING,
|
||||
getContext()));
|
||||
side.initialize();
|
||||
side.initialize(builder);
|
||||
}
|
||||
|
||||
// Both sides are initialized. The whole device is initialized.
|
||||
synchronized (this) {
|
||||
if (leftSide != null && leftSide.getState() == GBDevice.State.INITIALIZED &&
|
||||
rightSide != null && rightSide.getState() == GBDevice.State.INITIALIZED) {
|
||||
// Both sides are initialized. The whole device is initialized, don't use a device
|
||||
// index here. Device 0 is the device that the reset of GB sees.
|
||||
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED,
|
||||
getContext()));
|
||||
// This means that both sides have been connected to and basic info has been collected.
|
||||
@@ -160,8 +179,8 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
// individual initialization. We don't know what thread we are handling the update state
|
||||
// event on, so to be safe, schedule these as a background task.
|
||||
backgroundTasksHandler.postDelayed(() -> {
|
||||
leftSide.postInitialize();
|
||||
rightSide.postInitialize();
|
||||
leftSide.postInitializeLeft();
|
||||
rightSide.postInitializeRight();
|
||||
onSetTime();
|
||||
}, 200);
|
||||
}
|
||||
@@ -180,6 +199,11 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
leftSide = null;
|
||||
rightSide = null;
|
||||
|
||||
// Stop listening for intent actions
|
||||
if (intentReceiver != null) {
|
||||
getContext().unregisterReceiver(intentReceiver);
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -211,8 +235,7 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
return leftSide;
|
||||
} else if (deviceIdx == G1Constants.Side.RIGHT.getDeviceIndex()) {
|
||||
rightSide = new G1SideManager(G1Constants.Side.RIGHT, backgroundTasksHandler,
|
||||
getQueue, handleEvent, this::getDevicePrefs,
|
||||
rx, tx);
|
||||
getQueue, handleEvent, this::getDevicePrefs, rx, tx);
|
||||
return rightSide;
|
||||
}
|
||||
|
||||
@@ -229,6 +252,20 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
return null;
|
||||
}
|
||||
|
||||
private class IntentReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action != null && action.equals(G1Constants.INTENT_TOGGLE_SILENT_MODE)) {
|
||||
GBDevice device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
if (device != null && device.equals(getDevice())) {
|
||||
// We don't know what thread is handling this event, schedule the BLE to run in
|
||||
// the background.
|
||||
backgroundTasksHandler.post(G1DeviceSupport.this::onToggleSilentMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Below are all the onXXX() handlers overridden from the base class. //
|
||||
@@ -277,9 +314,17 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
*/
|
||||
@Override
|
||||
public void onSendConfiguration(String config) {
|
||||
// Forward to both sides.
|
||||
if (leftSide != null) leftSide.onSendConfiguration(config);
|
||||
if (rightSide != null) rightSide.onSendConfiguration(config);
|
||||
switch (config) {
|
||||
case DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_ACTIVATION_ANGLE:
|
||||
// This setting is only sent to the right arm.
|
||||
if (rightSide != null) rightSide.onSendConfiguration(config);
|
||||
break;
|
||||
default:
|
||||
// Forward to both sides.
|
||||
if (leftSide != null) leftSide.onSendConfiguration(config);
|
||||
if (rightSide != null) rightSide.onSendConfiguration(config);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -289,31 +334,48 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
boolean use12HourFormat = getDevicePrefs().getTimeFormat().equals(
|
||||
DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_12H);
|
||||
Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
long timeMilliseconds = c.getTimeInMillis();
|
||||
long tzOffset = TimeZone.getDefault().getOffset(timeMilliseconds);
|
||||
timeMilliseconds += tzOffset;
|
||||
long currentMilliseconds = c.getTimeInMillis();
|
||||
long tzOffset = TimeZone.getDefault().getOffset(currentMilliseconds);
|
||||
long timeMilliseconds = currentMilliseconds + tzOffset;
|
||||
|
||||
// Check if the GB settings are set to metric, if not, set the temp to use Fahrenheit.
|
||||
String metricString = GBApplication.getContext().getString(R.string.p_unit_metric);
|
||||
boolean useFahrenheit = !GBApplication.getPrefs().getString(SettingsActivity.PREF_MEASUREMENT_SYSTEM, metricString).equals(metricString);
|
||||
|
||||
// This block is synchronized. We do not want two calls to overlap, otherwise the lenses
|
||||
// could get skewed with different values.
|
||||
synchronized (sendTimeLock) {
|
||||
// Send the left the time synchronously, then once a response is received, send the right.
|
||||
// The glasses will ignore the command on the right lens if it arrives before the left.
|
||||
G1Communications.CommandHandler leftCommandHandler =
|
||||
new G1Communications.CommandTimeAndWeather(
|
||||
timeMilliseconds, use12HourFormat, /*weatherInfo=*/ null, useFahrenheit);
|
||||
leftSide.send(leftCommandHandler);
|
||||
if (!leftCommandHandler.waitForResponsePayload()) {
|
||||
LOG.error("Set time on left lens timed out");
|
||||
getDevice().setState(GBDevice.State.WAITING_FOR_RECONNECT);
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
// Run in the background in case the command hangs and this was run from the UI thread.
|
||||
backgroundTasksHandler.post(() -> {
|
||||
// This block is synchronized. We do not want two calls to overlap, otherwise the lenses
|
||||
// could get skewed with different values.
|
||||
synchronized (sendTimeLock) {
|
||||
// Send the left the time synchronously, then once a response is received, send the right.
|
||||
// The glasses will ignore the command on the right lens if it arrives before the left.
|
||||
G1Communications.CommandHandler leftCommandHandler =
|
||||
new G1Communications.CommandSetTimeAndWeather(timeMilliseconds,
|
||||
use12HourFormat, useFahrenheit);
|
||||
leftSide.send(leftCommandHandler);
|
||||
if (!leftCommandHandler.waitForResponsePayload()) {
|
||||
LOG.error("Set time on left lens timed out");
|
||||
getDevice().setState(GBDevice.State.WAITING_FOR_RECONNECT);
|
||||
getDevice().sendDeviceUpdateIntent(getContext());
|
||||
}
|
||||
|
||||
rightSide.send(new G1Communications.CommandTimeAndWeather(
|
||||
timeMilliseconds, use12HourFormat, /*weatherInfo=*/ null, useFahrenheit));
|
||||
rightSide.send(new G1Communications.CommandSetTimeAndWeather(timeMilliseconds,
|
||||
use12HourFormat,
|
||||
useFahrenheit));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onToggleSilentMode() {
|
||||
if (leftSide == null || rightSide == null) return;
|
||||
|
||||
// If both lenses are in sync on what the status is, set them both. Otherwise, only set the
|
||||
// right one so they can be resynchronized.
|
||||
if(leftSide.getSilentModeStatus() == rightSide.getSilentModeStatus()){
|
||||
leftSide.onToggleSilentMode();
|
||||
rightSide.onToggleSilentMode();
|
||||
} else {
|
||||
rightSide.onToggleSilentMode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+186
-26
@@ -1,6 +1,8 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.evenrealities;
|
||||
|
||||
import android.bluetooth.BluetoothGattCharacteristic;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -42,9 +44,10 @@ public class G1SideManager {
|
||||
private final BluetoothGattCharacteristic tx;
|
||||
private final Runnable batteryRunner;
|
||||
private final Runnable heartBeatRunner;
|
||||
private final Runnable displaySettingsPreviewCloserRunner;
|
||||
private final Set<G1Communications.CommandHandler> commandHandlers;
|
||||
private short heartBeatSequence;
|
||||
private short globalSequence;
|
||||
private byte globalSequence;
|
||||
private boolean isSilentModeEnabled;
|
||||
private GBDevice.State state;
|
||||
|
||||
public G1SideManager(G1Constants.Side mySide, Handler backgroundTasksHandler,
|
||||
@@ -59,19 +62,29 @@ public class G1SideManager {
|
||||
this.rx = rx;
|
||||
this.tx = tx;
|
||||
this.batteryRunner = () -> {
|
||||
send(new G1Communications.CommandBatteryLevel(this::handleBatteryPayload));
|
||||
send(new G1Communications.CommandGetBatteryInfo(this::handleBatteryPayload));
|
||||
scheduleBatteryPolling();
|
||||
};
|
||||
|
||||
this.heartBeatRunner = () -> {
|
||||
send(new G1Communications.CommandHeartBeat(heartBeatSequence++));
|
||||
// We can send any command as a heart beat. The official app uses this one.
|
||||
send(new G1Communications.CommandGetSilentModeSettings(null));
|
||||
scheduleHeatBeat();
|
||||
};
|
||||
this.displaySettingsPreviewCloserRunner = () -> {
|
||||
DevicePrefs prefs = getDevicePrefs();
|
||||
send(new G1Communications.CommandSetDisplaySettings(
|
||||
false /* preview */,
|
||||
(byte)prefs.getInt(DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_HEIGHT, 0),
|
||||
// Depth ranges from 1-9 instead of 0-8, so offset by one to convert from
|
||||
// the slider space.
|
||||
(byte)(prefs.getInt(DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_DEPTH, 0) + 1)));
|
||||
};
|
||||
this.commandHandlers = new HashSet<>();
|
||||
|
||||
// Non Finals
|
||||
this.heartBeatSequence = 0;
|
||||
this.globalSequence = 0;
|
||||
this.isSilentModeEnabled = false;
|
||||
this.state = GBDevice.State.CONNECTED;
|
||||
}
|
||||
|
||||
@@ -99,7 +112,7 @@ public class G1SideManager {
|
||||
return state;
|
||||
}
|
||||
|
||||
public void initialize() {
|
||||
public void initialize(TransactionBuilder transaction) {
|
||||
// The glasses will auto disconnect after 30 seconds of no data on the wire.
|
||||
// Schedule a heartbeat task. If this is not enabled, the glasses will disconnect and be
|
||||
// useless to the user.
|
||||
@@ -111,21 +124,77 @@ public class G1SideManager {
|
||||
state = GBDevice.State.INITIALIZED;
|
||||
}
|
||||
|
||||
public void postInitialize() {
|
||||
send(new G1Communications.CommandBatteryLevel(this::handleBatteryPayload));
|
||||
send(new G1Communications.CommandFirmwareInfo(this::handleFirmwareInfoPayload));
|
||||
public byte getSilentModeStatus() {
|
||||
return isSilentModeEnabled ? G1Constants.SilentStatus.ENABLE : G1Constants.SilentStatus.DISABLE;
|
||||
}
|
||||
|
||||
private void postInitializeCommon(TransactionBuilder transaction) {
|
||||
sendInTransaction(transaction, new G1Communications.CommandGetBatteryInfo(this::handleBatteryPayload));
|
||||
sendInTransaction(transaction, new G1Communications.CommandGetFirmwareInfo(this::handleFirmwareInfoPayload));
|
||||
sendInTransaction(transaction, new G1Communications.CommandGetSilentModeSettings(this::handleSilentStatusPayload));
|
||||
}
|
||||
|
||||
public void postInitializeLeft() {
|
||||
TransactionBuilder transaction =
|
||||
new TransactionBuilder("post_initialize_left_" + mySide.getDeviceIndex());
|
||||
postInitializeCommon(transaction);
|
||||
|
||||
// These can be sent to both, but the left lens is used as the master for these settings.
|
||||
sendInTransaction(transaction, new G1Communications.CommandGetDisplaySettings(this::handleDisplaySettingsPayload));
|
||||
sendInTransaction(transaction, new G1Communications.CommandGetBrightnessSettings(this::handleBrightnessSettingsPayload));
|
||||
transaction.queue(getQueue());
|
||||
}
|
||||
|
||||
public void postInitializeRight() {
|
||||
TransactionBuilder transaction =
|
||||
new TransactionBuilder( "post_initialize_right_" + mySide.getDeviceIndex());
|
||||
postInitializeCommon(transaction);
|
||||
|
||||
// This settings are only sent to the right lens in the official app, so we copy that.
|
||||
sendInTransaction(transaction, new G1Communications.CommandGetHeadGestureSettings(this::handleHeadGestureSettingsPayload));
|
||||
// This setting uses the right lens as the master for the setting simply to balance the amount
|
||||
// of commands being sent to the left vs right.
|
||||
sendInTransaction(transaction, new G1Communications.CommandGetWearDetectionSettings(this::handleWearDetectionSettingsPayload));
|
||||
transaction.queue(getQueue());
|
||||
}
|
||||
|
||||
public void onSendConfiguration(String config) {
|
||||
DevicePrefs prefs = getDevicePrefs();
|
||||
switch (config) {
|
||||
// Reschedule battery polling. The new schedule may be disabled.
|
||||
case DeviceSettingsPreferenceConst.PREF_BATTERY_POLLING_ENABLE:
|
||||
case DeviceSettingsPreferenceConst.PREF_BATTERY_POLLING_INTERVAL:
|
||||
scheduleBatteryPolling();
|
||||
break;
|
||||
case DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_HEIGHT:
|
||||
case DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_DEPTH:
|
||||
sendDisplaySettings(prefs);
|
||||
break;
|
||||
case DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_ACTIVATION_ANGLE:
|
||||
send(new G1Communications.CommandSetHeadGestureSettings(
|
||||
(byte)prefs.getInt(DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_ACTIVATION_ANGLE, 40)));
|
||||
break;
|
||||
case DeviceSettingsPreferenceConst.PREF_SCREEN_AUTO_BRIGHTNESS:
|
||||
case DeviceSettingsPreferenceConst.PREF_SCREEN_BRIGHTNESS:
|
||||
send(new G1Communications.CommandSetBrightnessSettings(
|
||||
prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SCREEN_AUTO_BRIGHTNESS, true),
|
||||
(byte)prefs.getInt(DeviceSettingsPreferenceConst.PREF_SCREEN_BRIGHTNESS, 0x2A)));// TODO: Add a constant for the max value?
|
||||
break;
|
||||
case DeviceSettingsPreferenceConst.PREF_WEAR_SENSOR_TOGGLE:
|
||||
send(new G1Communications.CommandSetWearDetectionSettings(
|
||||
prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_WEAR_SENSOR_TOGGLE, true)));
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public void onToggleSilentMode() {
|
||||
isSilentModeEnabled = !isSilentModeEnabled;
|
||||
G1Communications.CommandHandler commandHandler =
|
||||
new G1Communications.CommandSetSilentModeSettings(isSilentModeEnabled);
|
||||
send(commandHandler);
|
||||
}
|
||||
|
||||
private void scheduleHeatBeat() {
|
||||
backgroundTasksHandler.removeCallbacksAndMessages(heartBeatRunner);
|
||||
LOG.debug("Starting heartbeat runner delayed by {}ms", G1Constants.HEART_BEAT_DELAY_MS);
|
||||
@@ -134,30 +203,56 @@ public class G1SideManager {
|
||||
|
||||
private void scheduleBatteryPolling() {
|
||||
backgroundTasksHandler.removeCallbacksAndMessages(batteryRunner);
|
||||
DevicePrefs prefs = getDevicePrefs();
|
||||
if (prefs.getBatteryPollingEnabled()) {
|
||||
int interval_minutes = prefs.getBatteryPollingIntervalMinutes();
|
||||
int interval = interval_minutes * 60 * 1000;
|
||||
LOG.debug("Starting battery runner delayed by {} ({} minutes)", interval,
|
||||
interval_minutes);
|
||||
backgroundTasksHandler.postDelayed(batteryRunner, interval);
|
||||
DevicePrefs prefs = getDevicePrefs();
|
||||
if (prefs.getBatteryPollingEnabled()) {
|
||||
int interval_minutes = prefs.getBatteryPollingIntervalMinutes();
|
||||
int interval = interval_minutes * 60 * 1000;
|
||||
LOG.debug("Starting battery runner delayed by {} ({} minutes)", interval,
|
||||
interval_minutes);
|
||||
backgroundTasksHandler.postDelayed(batteryRunner, interval);
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized short getNextSequence() {
|
||||
private synchronized byte getNextSequence() {
|
||||
// Synchronized so the sequence increments atomically.
|
||||
// This number will eventually overflow, and that is fine. The sequence number is just to
|
||||
// match the request and response together.
|
||||
return globalSequence++;
|
||||
}
|
||||
|
||||
public void send(G1Communications.CommandHandler command) {
|
||||
TransactionBuilder builder =
|
||||
new TransactionBuilder(command.getName() + "_" + mySide.getDeviceIndex());
|
||||
sendInTransaction(builder, command);
|
||||
builder.queue(getQueue());
|
||||
private synchronized void sendDisplaySettings(DevicePrefs prefs) {
|
||||
// Synchronized so that there can only ever be one background task.
|
||||
// Clear any existing runner in case the user has changed the value multiple times
|
||||
// before th delay expired.
|
||||
backgroundTasksHandler.removeCallbacksAndMessages(displaySettingsPreviewCloserRunner);
|
||||
|
||||
// The glasses expect the setting to
|
||||
send(new G1Communications.CommandSetDisplaySettings(
|
||||
true /* preview */,
|
||||
(byte)prefs.getInt(DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_HEIGHT, 0),
|
||||
// Depth ranges from 1-9 instead of 0-8, so offset by one to convert from
|
||||
// the slider space.
|
||||
(byte)(prefs.getInt(DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_DEPTH, 0) + 1)));
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
// On newer APIs, use the runner as the token.
|
||||
backgroundTasksHandler.postDelayed(displaySettingsPreviewCloserRunner,
|
||||
displaySettingsPreviewCloserRunner,
|
||||
G1Constants.DISPLAY_SETTINGS_PREVIEW_DELAY);
|
||||
} else {
|
||||
backgroundTasksHandler.postDelayed(displaySettingsPreviewCloserRunner,
|
||||
G1Constants.DISPLAY_SETTINGS_PREVIEW_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendInTransaction(TransactionBuilder builder, G1Communications.CommandHandler command) {
|
||||
public void send(G1Communications.CommandHandler command) {
|
||||
TransactionBuilder transaction =
|
||||
new TransactionBuilder(command.getName() + "_" + mySide.getDeviceIndex());
|
||||
sendInTransaction(transaction, command);
|
||||
transaction.queue(getQueue());
|
||||
}
|
||||
|
||||
private void sendInTransaction(TransactionBuilder transaction, G1Communications.CommandHandler command) {
|
||||
// Calling getNextSequence() will advance the global sequence, if the command doesn't need
|
||||
// a sequence number, don't call it so we don't waste a sequence number.
|
||||
if (command.needsGlobalSequence()) {
|
||||
@@ -167,7 +262,7 @@ public class G1SideManager {
|
||||
LOG.debug("Send command {} on side {}", command.getName(), mySide.getDeviceIndex());
|
||||
|
||||
// Write the packet to the BLE txn.
|
||||
builder.write(tx, command.serialize());
|
||||
transaction.write(tx, command.serialize());
|
||||
|
||||
// If this command expects a response, register the handler.
|
||||
if (command.expectResponse()) {
|
||||
@@ -206,8 +301,10 @@ public class G1SideManager {
|
||||
|
||||
public boolean handlePayload(byte[] payload) {
|
||||
for (G1Communications.CommandHandler commandHandler : commandHandlers) {
|
||||
LOG.debug("Got response payload for command {} on side {}: {}", commandHandler.getName(), mySide.getDeviceIndex(), Logging.formatBytes(payload));
|
||||
if (commandHandler.responseMatches(payload)) {
|
||||
LOG.debug("Got response payload for command {} on side {}: {}",
|
||||
commandHandler.getName(), mySide.getDeviceIndex(),
|
||||
Logging.formatBytes(payload));
|
||||
synchronized (commandHandlers) {
|
||||
commandHandlers.remove(commandHandler);
|
||||
commandHandler.setResponsePayload(payload);
|
||||
@@ -217,7 +314,19 @@ public class G1SideManager {
|
||||
return callback != null && callback.apply(payload);
|
||||
}
|
||||
}
|
||||
LOG.debug("Unhandled payload on side {}: {}", mySide.getDeviceIndex(), Logging.formatBytes(payload));
|
||||
|
||||
// These can come in unprompted from the glasses, call the correct handler based on what the
|
||||
// command is.
|
||||
if (payload.length > 1) {
|
||||
if (payload[0] == G1Constants.CommandId.DEVICE_ACTION.id) {
|
||||
return handleDeviceActionPayload(payload);
|
||||
} else if (payload[0] == G1Constants.CommandId.DASHBOARD.id) {
|
||||
return handleDashboardPayload(payload);
|
||||
}
|
||||
}
|
||||
|
||||
LOG.debug("Unhandled payload on side {}: {}",
|
||||
mySide.getDeviceIndex(), Logging.formatBytes(payload));
|
||||
|
||||
// Not handled by any handlers.
|
||||
return false;
|
||||
@@ -254,4 +363,55 @@ public class G1SideManager {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean handleSilentStatusPayload(byte[] payload) {
|
||||
isSilentModeEnabled = G1Communications.CommandGetSilentModeSettings.isEnabled(payload);
|
||||
return true;
|
||||
}
|
||||
private boolean handleDisplaySettingsPayload(byte[] payload) {
|
||||
SharedPreferences.Editor editor = getDevicePrefs().getPreferences().edit();
|
||||
editor.putInt(DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_HEIGHT,
|
||||
G1Communications.CommandGetDisplaySettings.getHeight(payload));
|
||||
// Depth is indexed is 1-9, so subtract 1 to map it to the 0-8 of the slider.
|
||||
editor.putInt(DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_DEPTH,
|
||||
G1Communications.CommandGetDisplaySettings.getDepth(payload) - 1);
|
||||
editor.apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean handleHeadGestureSettingsPayload(byte[] payload) {
|
||||
SharedPreferences.Editor editor = getDevicePrefs().getPreferences().edit();
|
||||
editor.putInt(DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_ACTIVATION_ANGLE,
|
||||
G1Communications.CommandGetHeadGestureSettings.getActivationAngle(payload));
|
||||
editor.apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean handleBrightnessSettingsPayload(byte[] payload) {
|
||||
SharedPreferences.Editor editor = getDevicePrefs().getPreferences().edit();
|
||||
editor.putBoolean(DeviceSettingsPreferenceConst.PREF_SCREEN_AUTO_BRIGHTNESS,
|
||||
G1Communications.CommandGetBrightnessSettings.isAutoBrightnessEnabled(payload));
|
||||
editor.putInt(DeviceSettingsPreferenceConst.PREF_SCREEN_BRIGHTNESS,
|
||||
G1Communications.CommandGetBrightnessSettings.getBrightnessLevel(payload));
|
||||
editor.apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean handleWearDetectionSettingsPayload(byte[] payload) {
|
||||
SharedPreferences.Editor editor = getDevicePrefs().getPreferences().edit();
|
||||
editor.putBoolean(DeviceSettingsPreferenceConst.PREF_WEAR_SENSOR_TOGGLE,
|
||||
G1Communications.CommandGetWearDetectionSettings.isEnabled(payload));
|
||||
editor.apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean handleDeviceActionPayload(byte[] payload) {
|
||||
LOG.debug("Device Action payload on side {}: {}", mySide.getDeviceIndex(), Logging.formatBytes(payload));
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean handleDashboardPayload(byte[] payload) {
|
||||
LOG.debug("Dashboard payload on side {}: {}", mySide.getDeviceIndex(), Logging.formatBytes(payload));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#7E7E7E"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M2,21h20"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M2,21 L16,5"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M7.5,15 A5,5 0 0,1 10.5,21"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,46 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#7E7E7E"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:pathData="M2,21h20"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M2,21 L14,7"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M7.5,15 A5,5 0 0,1 10.5,21"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"/>
|
||||
|
||||
<path
|
||||
android:pathData="M7.5,5 A5,5 0 0,1 17,15"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"/>
|
||||
|
||||
<path
|
||||
android:pathData="M8,2 L7.5,5 L10,6"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:strokeLineCap="round" />
|
||||
|
||||
|
||||
</vector>
|
||||
@@ -0,0 +1,46 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#7E7E7E"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:pathData="M10.8,2.3 L12,1 L13.2,2.3"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:strokeLineCap="round" />
|
||||
|
||||
<path
|
||||
android:pathData="M11.7,1.4 L12.3,1.4 L12.8,7 L11.2,7 Z"
|
||||
android:fillColor="#FFFFFFFF" />
|
||||
|
||||
<path
|
||||
android:pathData="M12,7v0"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:strokeWidth="1.6"
|
||||
android:strokeLineCap="round"
|
||||
android:fillColor="#00000000" />
|
||||
|
||||
<path
|
||||
android:pathData="M12,10v0"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:strokeWidth="2.1"
|
||||
android:strokeLineCap="round"
|
||||
android:fillColor="#00000000" />
|
||||
|
||||
<path
|
||||
android:pathData="M10.95,10 L13.05,10 L14,21 L10,21 Z"
|
||||
android:fillColor="#FFFFFFFF" />
|
||||
|
||||
<path
|
||||
android:pathData="M7,16 L12,21 L17,16"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="3"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:strokeLineCap="round" />
|
||||
|
||||
</vector>
|
||||
@@ -0,0 +1,36 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#7E7E7E"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
|
||||
android:pathData="M12,14v7"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M12,3v7"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M9,18l3,3l3,-3"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:strokeLineCap="round"/>
|
||||
<path
|
||||
android:pathData="M9,6l3,-3l3,3"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,16 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="#7E7E7E"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8z"
|
||||
/>
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M6,11h12v2h-12z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,31 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#7E7E7E"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M16 20c-0.29 0-0.56-0.06-0.76-0.15-0.71-0.37-1.21-0.88-1.71-2.38-0.51-1.56-1.47-2.29-2.39-3-0.79-0.61-1.61-1.24-2.32-2.53C8.29 10.98 8 9.93 8 9c0-2.8 2.2-5 5-5s5 2.2 5 5h2c0-3.93-3.07-7-7-7S6 5.07 6 9c0 1.26 0.38 2.65 1.07 3.9 0.91 1.65 1.98 2.48 2.85 3.15 0.81 0.62 1.39 1.07 1.71 2.05 0.6 1.82 1.37 2.84 2.73 3.55C14.87 21.88 15.43 22 16 22c2.21 0 4-1.79 4-4h-2c0 1.1-0.9 2-2 2z"/>
|
||||
|
||||
<path
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:strokeLineCap="round"
|
||||
android:pathData="M5,6h2 M5,6 l -3,4"/>
|
||||
<path
|
||||
android:strokeWidth="2"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:pathData="M18,6h5 "/>
|
||||
|
||||
<!-- Need to make this look nicer -->
|
||||
<!-- <path-->
|
||||
<!-- android:strokeLineJoin="round"-->
|
||||
<!-- android:strokeWidth="3"-->
|
||||
<!-- android:strokeColor="#FFFFFFFF"-->
|
||||
<!-- android:strokeLineCap="round"-->
|
||||
<!-- android:pathData="M13,9 l 2,2 M15,11 l 1,2"/>-->
|
||||
|
||||
</vector>
|
||||
@@ -1124,6 +1124,7 @@
|
||||
<string name="silent_mode_normal_vibrate">Normal / Vibrate</string>
|
||||
<string name="silent_mode_normal_silent">Normal / Silent</string>
|
||||
<string name="silent_mode_vibrate_silent">Vibrate / Silent</string>
|
||||
<string name="silent_mode">Silent Mode</string>
|
||||
<string name="prefs_always_on_display">Always On Display</string>
|
||||
<string name="prefs_always_on_display_follow_watchface">Style follows Watchface</string>
|
||||
<string name="prefs_always_on_display_style">Style</string>
|
||||
@@ -3959,4 +3960,12 @@
|
||||
<string name="pref_huawei_activity_reminder_goal_reached_title">Goal Reached</string>
|
||||
<string name="pref_huawei_activity_reminder_goal_reached_summary">Get a reminder when you complete rings</string>
|
||||
<string name="body_energy_legend_average">30-Day Average</string>
|
||||
<string name="pref_even_realities_g1_screen_location_title">Screen Location</string>
|
||||
<string name="pref_even_realities_g1_screen_location_summary">Changes the vertical location of the display</string>
|
||||
<string name="pref_even_realities_g1_screen_depth_title">Screen Depth</string>
|
||||
<string name="pref_even_realities_g1_screen_depth_summary">Changes the binocular separation and apparent depth of the display</string>
|
||||
<string name="pref_even_realities_g1_screen_activation_angle_title">Activation Angle</string>
|
||||
<string name="pref_even_realities_g1_screen_activation_angle_summary">Changes how high the wearer needs to lift their head for the dashboard to be shown</string>
|
||||
<string name="pref_even_realities_g1_screen_calibrate_angle_title">Calibrate Angle</string>
|
||||
<string name="pref_even_realities_g1_screen_calibrate_angle_summary">Zeroes the angle of the glasses, look straight ahead and tap here</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- <PreferenceCategory android:title="@string/pref_header_display">-->
|
||||
<SeekBarPreference
|
||||
android:key="pref_even_realities_g1_screen_height"
|
||||
android:defaultValue="0"
|
||||
android:max="8"
|
||||
android:icon="@drawable/ic_arrows_up_down"
|
||||
android:title="@string/pref_even_realities_g1_screen_location_title"
|
||||
android:summary="@string/pref_even_realities_g1_screen_location_summary" />
|
||||
<SeekBarPreference
|
||||
android:key="pref_even_realities_g1_screen_depth"
|
||||
android:defaultValue="0"
|
||||
android:icon="@drawable/ic_arrow_depth"
|
||||
android:max="8"
|
||||
android:title="@string/pref_even_realities_g1_screen_depth_title"
|
||||
android:summary="@string/pref_even_realities_g1_screen_depth_summary" />
|
||||
<SeekBarPreference
|
||||
android:key="pref_even_realities_g1_screen_activation_angle"
|
||||
android:defaultValue="40"
|
||||
android:icon="@drawable/ic_angle"
|
||||
android:max="60"
|
||||
android:title="@string/pref_even_realities_g1_screen_activation_angle_title"
|
||||
android:summary="@string/pref_even_realities_g1_screen_activation_angle_summary" />
|
||||
<Preference
|
||||
android:defaultValue="0"
|
||||
android:icon="@drawable/ic_angle_calibrate"
|
||||
android:enabled="false"
|
||||
android:key="pref_even_realities_g1_screen_calibrate_angle"
|
||||
android:summary="@string/pref_even_realities_g1_screen_calibrate_angle_summary"
|
||||
android:title="@string/pref_even_realities_g1_screen_calibrate_angle_title" />
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:disableDependentsState="true"
|
||||
android:icon="@drawable/ic_wb_sunny"
|
||||
android:key="screen_auto_brightness"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:summary="@string/pref_screen_auto_brightness_summary"
|
||||
android:title="@string/pref_screen_auto_brightness_title" />
|
||||
<SeekBarPreference
|
||||
android:key="screen_brightness"
|
||||
android:dependency="screen_auto_brightness"
|
||||
android:defaultValue="42"
|
||||
android:max="42"
|
||||
android:icon="@drawable/ic_wb_sunny"
|
||||
android:title="@string/pref_screen_brightness" />
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:icon="@drawable/ic_ear_glasses"
|
||||
android:key="wear_sensor_toggle"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:summary="@string/pref_wear_sensor_summary"
|
||||
android:title="@string/pref_wear_sensor_title" />
|
||||
|
||||
<!-- </PreferenceCategory>-->
|
||||
</PreferenceScreen>
|
||||
Reference in New Issue
Block a user