mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Even Realities G1: Make the heartbeat more aggressive on disconnect
I switched from pixel 8 to pixel 10 and the 10 is much more aggressive at putting threads to sleep. This change changes the logic to make the heartbeat more and more aggressive the more unexpected disconnects that there are. Even Realities G1: Add Calendar Support This change adds support for syncing the calendar to the glasses. It also cleans up the settings menu and adds a few dummy options for touch controls that don't currently do anything. Even Realities G1: Clean up the command ID constants This should be purely a refactor with no functional changes. The change renames all the command IDs, it adds the ones that are missing and unifies the name of all of them. It also breaks out the known subfunctions into their own structures. Finally, it reorders the Command objects to be in the same order as the command ids. Even Realities G1: Set the language on the glasses to match the app locale
This commit is contained in:
+3
-1
@@ -73,6 +73,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
|
||||
public class SettingsActivity extends AbstractSettingsActivityV2 {
|
||||
public static final String PREF_MEASUREMENT_SYSTEM = "measurement_system";
|
||||
public static final String PREF_LANGUAGE = "language";
|
||||
|
||||
@Override
|
||||
protected PreferenceFragmentCompat newFragment() {
|
||||
@@ -185,13 +186,14 @@ public class SettingsActivity extends AbstractSettingsActivityV2 {
|
||||
}
|
||||
}
|
||||
|
||||
pref = findPreference("language");
|
||||
pref = findPreference(PREF_LANGUAGE);
|
||||
if (pref != null) {
|
||||
pref.setOnPreferenceChangeListener((preference, newVal) -> {
|
||||
String newLang = newVal.toString();
|
||||
try {
|
||||
GBApplication.setLanguage(newLang);
|
||||
requireActivity().recreate();
|
||||
invokeLater(() -> GBApplication.deviceService().onSendConfiguration(PREF_LANGUAGE));
|
||||
} catch (Exception ex) {
|
||||
GB.toast(requireContext().getApplicationContext(),
|
||||
"Error setting language: " + ex.getLocalizedMessage(),
|
||||
|
||||
+5
@@ -706,7 +706,12 @@ public class DeviceSettingsPreferenceConst {
|
||||
public static final String PREF_CONNECTION_FORCE_LEGACY_GATT = "connection_force_legacy_gatt";
|
||||
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_ENABLE = "pref_even_realities_g1_screen_activation_angle_enable";
|
||||
public static final String PREF_EVEN_REALITIES_SCREEN_ACTIVATION_ANGLE = "pref_even_realities_g1_screen_activation_angle";
|
||||
public static final String PREF_EVEN_REALITIES_TOUCH_DOUBLE_LEFT = "pref_even_realities_g1_touch_double_left";
|
||||
public static final String PREF_EVEN_REALITIES_TOUCH_DOUBLE_RIGHT = "pref_even_realities_g1_touch_double_right";
|
||||
public static final String PREF_EVEN_REALITIES_TOUCH_LONG_LEFT = "pref_even_realities_g1_touch_long_left";
|
||||
public static final String PREF_EVEN_REALITIES_TOUCH_LONG_RIGHT = "pref_even_realities_g1_touch_long_right";
|
||||
|
||||
public static final String PREF_DEVICE_STRESS_TEST_CONNECT_COUNT = "pref_device_stress_test_connect_count";
|
||||
public static final String PREF_DEVICE_STRESS_TEST_CONNECT_PARALLEL = "pref_device_stress_test_connect_parallel";
|
||||
|
||||
+1
@@ -30,6 +30,7 @@ public enum DeviceSpecificSettingsScreen {
|
||||
CALENDAR("pref_screen_calendar", R.xml.devicesettings_root_calendar),
|
||||
CALLS_AND_NOTIFICATIONS("pref_screen_calls_and_notifications", R.xml.devicesettings_root_calls_and_notifications),
|
||||
CONNECTION("pref_screen_connection", R.xml.devicesettings_root_connection),
|
||||
DASHBOARD("pref_screen_dashboard", R.xml.devicesettings_root_dashboard),
|
||||
DEVELOPER("pref_screen_developer", R.xml.devicesettings_root_developer),
|
||||
DISPLAY("pref_screen_display", R.xml.devicesettings_root_display),
|
||||
GENERIC("pref_screen_generic", R.xml.devicesettings_root_generic),
|
||||
|
||||
+37
-13
@@ -75,6 +75,11 @@ public class G1DeviceCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
return R.drawable.ic_device_even_realities_g1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceKind getDeviceKind(@NonNull GBDevice device) {
|
||||
return DeviceKind.SMART_GLASSES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBondingStyle() {
|
||||
return BONDING_STYLE_LAZY;
|
||||
@@ -194,21 +199,40 @@ public class G1DeviceCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
|
||||
final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
|
||||
if (device.isConnected()) {
|
||||
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_screen_on_on_notifications);
|
||||
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_screen_on_on_notifications_timeout);
|
||||
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_even_realities_g1_display);
|
||||
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_timeformat);
|
||||
|
||||
final List<Integer> developer =
|
||||
deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
|
||||
developer.add(R.xml.devicesettings_header_system);
|
||||
developer.add(R.xml.devicesettings_debug_logs_toggle);
|
||||
}
|
||||
final List<Integer> dashboard =
|
||||
deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DASHBOARD);
|
||||
dashboard.add(R.xml.devicesettings_timeformat);
|
||||
dashboard.add(R.xml.devicesettings_even_realities_g1_dashboard);
|
||||
|
||||
final List<Integer> display = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DISPLAY);
|
||||
display.add(R.xml.devicesettings_even_realities_g1_display);
|
||||
|
||||
final List<Integer> notifications = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.NOTIFICATIONS);
|
||||
notifications.add(R.xml.devicesettings_screen_on_on_notifications);
|
||||
notifications.add(R.xml.devicesettings_screen_on_on_notifications_timeout);
|
||||
|
||||
final List<Integer> touch = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.TOUCH_OPTIONS);
|
||||
touch.add(R.xml.devicesettings_even_realities_g1_touch);
|
||||
|
||||
final List<Integer> calendar = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CALENDAR);
|
||||
calendar.add(R.xml.devicesettings_sync_calendar);
|
||||
|
||||
final List<Integer> developer =
|
||||
deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.DEVELOPER);
|
||||
developer.add(R.xml.devicesettings_header_system);
|
||||
developer.add(R.xml.devicesettings_debug_logs_toggle);
|
||||
|
||||
deviceSpecificSettings.addConnectedPreferences(DeviceSpecificSettingsScreen.DASHBOARD.getKey(),
|
||||
DeviceSpecificSettingsScreen.DISPLAY.getKey(),
|
||||
DeviceSpecificSettingsScreen.NOTIFICATIONS.getKey(),
|
||||
DeviceSpecificSettingsScreen.TOUCH_OPTIONS.getKey(),
|
||||
DeviceSpecificSettingsScreen.DEVELOPER.getKey()
|
||||
);
|
||||
|
||||
return deviceSpecificSettings;
|
||||
}
|
||||
|
||||
@@ -222,7 +246,7 @@ public class G1DeviceCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeviceKind getDeviceKind(@NonNull GBDevice device) {
|
||||
return DeviceKind.SMART_GLASSES;
|
||||
public boolean supportsCalendarEvents(final GBDevice device) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,7 +431,13 @@ public final class BtLEQueue implements Thread.UncaughtExceptionHandler {
|
||||
}
|
||||
|
||||
if (forceDisconnect) {
|
||||
BluetoothDevice device = mBluetoothGatt.getDevice();
|
||||
// TODO: There is likely a race condition, the device and the gatt object should not be
|
||||
// null at this point. For multi queue objects, this seems to break, don't remove this
|
||||
// check.
|
||||
BluetoothDevice device = null;
|
||||
if (mBluetoothGatt != null) {
|
||||
device = mBluetoothGatt.getDevice();
|
||||
}
|
||||
LOG.warn("unhealthy disconnect {} {}", device == null ? "<UNKNOWN>" : device.getAddress(),
|
||||
BleNamesResolver.getStatusString(status));
|
||||
} else if (mBluetoothGatt != null) {
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.evenrealities;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Certain payloads are too large for one packet, so this class is a simple extension of the
|
||||
* G1CommandHandler that allows the subclass to send multiple packets.
|
||||
* This works by forcing the caller to pass in a callback to the "send" function they are using
|
||||
* and the ChunkG1CommandHandler will intercept calls to the Command callback to send the next
|
||||
* chunk in the packet. Once the response for the last chunk is sent, the passed in callback
|
||||
* will be sent.
|
||||
*/
|
||||
public abstract class G1ChunkedCommandHandler extends G1CommandHandler {
|
||||
private final Consumer<G1CommandHandler> sendCallback;
|
||||
private byte currentChunk;
|
||||
protected final byte[] payload;
|
||||
protected final byte chunkCount;
|
||||
private final byte[] sequenceIds;
|
||||
|
||||
protected static byte getChunkCountForPayloadLength(int payloadLength, int headerLength) {
|
||||
int maxChunkSize = G1Constants.MAX_PACKET_SIZE_BYTES - headerLength;
|
||||
return (byte)((payloadLength / maxChunkSize) + 1);
|
||||
}
|
||||
|
||||
protected G1ChunkedCommandHandler(byte[] sequenceIds, Consumer<G1CommandHandler> sendCallback,
|
||||
Function<byte[], Boolean> callback, byte[] payload) {
|
||||
super((byte)0, true, callback);
|
||||
this.sendCallback = sendCallback;
|
||||
this.currentChunk = 0;
|
||||
this.payload = payload;
|
||||
this.chunkCount = getChunkCountForPayloadLength(this.payload.length, getHeaderSize());
|
||||
this.sequenceIds = sequenceIds;
|
||||
}
|
||||
|
||||
@Override
|
||||
final public Function<byte[], Boolean> getCallback() {
|
||||
if (currentChunk < chunkCount) {
|
||||
// Return the callback which sends the next chunk.
|
||||
return this::sendNextChunk;
|
||||
} else {
|
||||
// Now that all the chunks have been received, return the user callback.
|
||||
return super.getCallback();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
final public byte[] serialize() {
|
||||
// Calculate the size, begin and end of the chunk.
|
||||
int maxPayloadSize = G1Constants.MAX_PACKET_SIZE_BYTES - getHeaderSize();
|
||||
int chunkBegin = this.currentChunk * maxPayloadSize;
|
||||
int chunkEnd = Math.min(this.payload.length,
|
||||
(this.currentChunk + 1) * maxPayloadSize);
|
||||
int payloadSize = chunkEnd - chunkBegin;
|
||||
|
||||
// Create the packet with space for the header.
|
||||
byte[] packet = new byte[getHeaderSize() + payloadSize];
|
||||
|
||||
// Get the next sequence in the list if sequence ids are provided.
|
||||
byte currentSequenceId = 0x00;
|
||||
if (sequenceIds != null) {
|
||||
currentSequenceId = sequenceIds[this.currentChunk];
|
||||
}
|
||||
|
||||
// Let the subclass write the header.
|
||||
writeHeader(this.currentChunk, currentSequenceId, this.chunkCount, packet);
|
||||
|
||||
// Copy the chunk of the payload into the packet.
|
||||
System.arraycopy(this.payload, chunkBegin, packet, getHeaderSize(), payloadSize);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
final public boolean responseMatches(byte[] payload) {
|
||||
byte currentSequenceId = 0x00;
|
||||
if (sequenceIds != null) {
|
||||
currentSequenceId = sequenceIds[this.currentChunk];
|
||||
}
|
||||
|
||||
if (chunkMatches(currentChunk, currentSequenceId, payload)) {
|
||||
// Advance the chunk when the response is received.
|
||||
currentChunk++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getName() {
|
||||
return getPacketName() + "_" + currentChunk;
|
||||
}
|
||||
|
||||
private boolean sendNextChunk(byte[] payload) {
|
||||
sendCallback.accept(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract boolean chunkMatches(byte currentChunk, byte currentSequence, byte[] payload);
|
||||
protected abstract void writeHeader(byte currentChunk, byte currentSequence, byte chunkCount, byte[] chunk);
|
||||
protected abstract int getHeaderSize();
|
||||
protected abstract String getPacketName();
|
||||
}
|
||||
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.evenrealities;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
public abstract class G1CommandHandler {
|
||||
protected final byte sequence;
|
||||
private final boolean expectResponse;
|
||||
private final Function<byte[], Boolean> callback;
|
||||
private byte[] responsePayload;
|
||||
private int retryCount;
|
||||
|
||||
protected G1CommandHandler(byte sequence, boolean expectResponse, Function<byte[], Boolean> callback) {
|
||||
this.sequence = sequence;
|
||||
this.expectResponse = expectResponse;
|
||||
this.callback = callback;
|
||||
this.responsePayload = null;
|
||||
this.retryCount = 0;
|
||||
}
|
||||
|
||||
protected G1CommandHandler(boolean expectResponse, Function<byte[], Boolean> callback) {
|
||||
/* sequence is not used */
|
||||
this((byte)0, expectResponse, callback);
|
||||
}
|
||||
|
||||
public int getTimeout() {
|
||||
return G1Constants.DEFAULT_COMMAND_TIMEOUT_MS;
|
||||
}
|
||||
public int getMaxRetryCount() {
|
||||
return G1Constants.DEFAULT_RETRY_COUNT;
|
||||
}
|
||||
private synchronized boolean continueWaiting() {
|
||||
return !hasResponsePayload() && hasRetryRemaining();
|
||||
}
|
||||
|
||||
public synchronized void notifyAttempt() {
|
||||
retryCount++;
|
||||
notify();
|
||||
}
|
||||
|
||||
public synchronized void setResponsePayload(byte[] payload) {
|
||||
this.responsePayload = payload;
|
||||
notify();
|
||||
}
|
||||
|
||||
public synchronized boolean hasRetryRemaining() {
|
||||
return retryCount < getMaxRetryCount();
|
||||
}
|
||||
public synchronized boolean hasResponsePayload() {
|
||||
return responsePayload != null;
|
||||
}
|
||||
|
||||
public boolean waitForResponsePayload() {
|
||||
// Go to sleep until the either a response is gotten or there is a timeout.
|
||||
while (continueWaiting()) {
|
||||
synchronized (this) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
// If the reties were exhausted return false to indicate that there was no response from
|
||||
// the glasses.
|
||||
return hasRetryRemaining();
|
||||
}
|
||||
|
||||
public byte[] getResponsePayload() {
|
||||
if (responsePayload == null) {
|
||||
throw new RuntimeException("Null payload. Did you call waitForPayload()?");
|
||||
}
|
||||
return responsePayload;
|
||||
}
|
||||
|
||||
public Function<byte[], Boolean> getCallback() {
|
||||
return callback;
|
||||
}
|
||||
|
||||
public boolean expectResponse() {
|
||||
return expectResponse;
|
||||
}
|
||||
|
||||
public int getRetryCount() {
|
||||
return retryCount;
|
||||
}
|
||||
|
||||
public abstract byte[] serialize();
|
||||
public abstract boolean responseMatches(byte[] payload);
|
||||
public abstract String getName();
|
||||
}
|
||||
|
||||
+621
-592
File diff suppressed because it is too large
Load Diff
+187
-83
@@ -28,8 +28,9 @@ public class G1Constants {
|
||||
// expensive from a battery budget than just sending the heartbeat message.
|
||||
public static final int HEART_BEAT_BASE_DELAY_MS = 8000;
|
||||
public static final int HEART_BEAT_TARGET_DELAY_MS = 25000;
|
||||
public static final int HEART_BEAT_MAX_DELAY_MODIFIER_MS = 10000;
|
||||
public static final int DEFAULT_COMMAND_TIMEOUT_MS = 5000;
|
||||
public static final int DISPLAY_SETTINGS_PREVIEW_DELAY = 3000;
|
||||
public static final int DISPLAY_SETTINGS_PREVIEW_DELAY = 5000;
|
||||
public static final int DEFAULT_RETRY_COUNT = 5;
|
||||
public static final int CASE_BATTERY_INDEX = 2;
|
||||
public static final String INTENT_TOGGLE_SILENT_MODE = "nodomain.freeyourgadget.gadgetbridge.evenrealities.silent_mode";
|
||||
@@ -42,6 +43,11 @@ public class G1Constants {
|
||||
public static final Pair<String, String>
|
||||
FIXED_NOTIFICATION_APP_ID = new Pair<>("nodomain.freeyourgadget.gadget", "Name");
|
||||
|
||||
// Only 4 pages with two events each is supported.
|
||||
public static final int MAX_CALENDAR_EVENTS = 8;
|
||||
// Show calendar events for 5 minutes after they have started.
|
||||
public static final int CALENDAR_EVENT_CLEAR_DELAY = 5 * 1000 * 60;
|
||||
|
||||
// Extract the L or R at the end of the device prefix.
|
||||
public static Side getSideFromFullName(String deviceName) {
|
||||
// Name will be "G1_XX_[L|R]_YYYYY"
|
||||
@@ -74,11 +80,11 @@ public class G1Constants {
|
||||
RIGHT(1, "right");
|
||||
|
||||
private final int deviceIndex;
|
||||
private final String stringPrefix;
|
||||
private final String name;
|
||||
|
||||
Side(int deviceIndex, String stringPrefix) {
|
||||
Side(int deviceIndex, String name) {
|
||||
this.deviceIndex = deviceIndex;
|
||||
this.stringPrefix = stringPrefix;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getDeviceIndex() {
|
||||
@@ -90,11 +96,15 @@ public class G1Constants {
|
||||
}
|
||||
|
||||
public String getAddressKey() {
|
||||
return stringPrefix + "_address";
|
||||
return name + "_address";
|
||||
}
|
||||
|
||||
public String getNameKey() {
|
||||
return stringPrefix + "_name";
|
||||
return name + "_name";
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,75 +117,164 @@ 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;
|
||||
public static final byte FAIL = (byte)0xCA;
|
||||
public static final byte DATA_CONTINUES = (byte)0xCB;
|
||||
}
|
||||
|
||||
// TODO: Lifted these from a different project, some of them are wrong.
|
||||
public enum CommandId {
|
||||
DASHBOARD_CONFIG((byte) 0x06),
|
||||
SYNC_SEQUENCE((byte) 0x22), // 0x05
|
||||
DASHBOARD_SHOWN((byte) 0x22), // 0x0A
|
||||
SYSTEM((byte) 0x23),
|
||||
HEARTBEAT((byte) 0x25),
|
||||
BATTERY_LEVEL((byte) 0x2C),
|
||||
SET_MTU((byte) 0x4D),
|
||||
NOTIFICATION((byte) 0x4B),
|
||||
FW_INFO_RESPONSE((byte) 0x6E),
|
||||
DEBUG_LOG((byte) 0xF4),
|
||||
DEVICE_EVENT((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),
|
||||
GET_SERIAL_NUMBER((byte) 0x34),
|
||||
GET_NOTIFICATION_DISPLAY_SETTINGS((byte) 0x3C),
|
||||
SET_NOTIFICATION_DISPLAY_SETTINGS((byte) 0x4F),
|
||||
SET_NOTIFICATION_APP_SETTINGS((byte) 0x04),
|
||||
SEND_NOTIFICATION((byte) 0x4B),
|
||||
SEND_CLEAR_NOTIFICATION((byte) 0x4C);
|
||||
public static class MessageId {
|
||||
public static final byte STATUS = (byte)0x22;
|
||||
public static final byte AUDIO = (byte)0xF1;
|
||||
public static final byte DEBUG = (byte)0xF4;
|
||||
public static final byte EVENT = (byte)0xF5;
|
||||
};
|
||||
|
||||
final public byte id;
|
||||
public static class CommandId {
|
||||
public static final byte ANTI_SHAKE_GET = (byte)0x2A;
|
||||
public static final byte BITMAP_SHOW = (byte)0x16;
|
||||
public static final byte BITMAP_HIDE = (byte)0x18;
|
||||
public static final byte BRIGHTNESS_GET = (byte)0x29;
|
||||
public static final byte BRIGHTNESS_SET = (byte)0x01;
|
||||
public static final byte DASHBOARD_SET = (byte)0x06;
|
||||
public static final byte DASHBOARD_QUICK_NOTE_CONTROL = (byte)0x1E;
|
||||
public static final byte DASHBOARD_CALENDAR_NEXT_UP_SET = (byte)0x58;
|
||||
public static final byte FILE_UPLOAD = (byte)0x15;
|
||||
public static final byte FILE_UPLOAD_COMPLETE = (byte)0x20;
|
||||
public static final byte HARDWARE_GET = (byte)0x3F;
|
||||
public static final byte HARDWARE_SET = (byte)0x26;
|
||||
public static final byte HARDWARE_DISPLAY_GET = (byte)0x3B;
|
||||
public static final byte HEAD_UP_ANGLE_GET = (byte)0x32;
|
||||
public static final byte HEAD_UP_ANGLE_SET = (byte)0x0B;
|
||||
public static final byte HEAD_UP_ACTION_SET = (byte)0x08;
|
||||
public static final byte HEAD_UP_CALIBRATION_CONTROL = (byte)0x10;
|
||||
public static final byte INFO_BATTERY_AND_FIRMWARE_GET = (byte)0x2C;
|
||||
public static final byte INFO_MAC_ADDRESS_GET = (byte)0x2D;
|
||||
public static final byte INFO_SERIAL_NUMBER_LENS_GET = (byte)0x33;
|
||||
public static final byte INFO_SERIAL_NUMBER_GLASSES_GET = (byte)0x34;
|
||||
public static final byte INFO_ESB_CHANNEL_GET = (byte)0x35;
|
||||
public static final byte INFO_ESB_NOTIFICATION_COUNT_GET = (byte)0x36;
|
||||
public static final byte INFO_TIME_SINCE_BOOT_GET = (byte)0x37;
|
||||
public static final byte INFO_BURIED_POINT_GET = (byte)0x3E;
|
||||
public static final byte LANGUAGE_SET = (byte)0x3D;
|
||||
public static final byte MICROPHONE_SET = (byte)0x0E;
|
||||
public static final byte MTU_SET = (byte)0x4D;
|
||||
public static final byte NAVIGATION_CONTROL = (byte)0x0A;
|
||||
public static final byte NOTIFICATION_APP_LIST_GET = (byte)0x2E;
|
||||
public static final byte NOTIFICATION_APP_LIST_SET = (byte)0x04;
|
||||
public static final byte NOTIFICATION_APPLE_GET = (byte)0x38;
|
||||
public static final byte NOTIFICATION_AUTO_DISPLAY_GET = (byte)0x3C;
|
||||
public static final byte NOTIFICATION_AUTO_DISPLAY_SET = (byte)0x4F;
|
||||
public static final byte NOTIFICATION_SEND_CONTROL = (byte)0x4B;
|
||||
public static final byte NOTIFICATION_CLEAR_CONTROL = (byte)0x4C;
|
||||
public static final byte SILENT_MODE_GET = (byte)0x2B;
|
||||
public static final byte SILENT_MODE_SET = (byte)0x03;
|
||||
public static final byte STATUS_GET = (byte)0x22;
|
||||
public static final byte STATUS_RUNNING_APP_GET = (byte)0x39;
|
||||
public static final byte SYSTEM_CONTROL = (byte)0x23;
|
||||
public static final byte TELEPROMPTER_CONTROL = (byte)0x09;
|
||||
public static final byte TELEPROMPTER_SUSPEND = (byte)0x24;
|
||||
public static final byte TELEPROMPTER_POSITION_SET = (byte)0x25;
|
||||
public static final byte TEXT_SET = (byte)0x4E;
|
||||
public static final byte TIMER_CONTROL = (byte)0x07;
|
||||
public static final byte TRANSCRIBE_CONTROL = (byte)0x0D;
|
||||
public static final byte TRANSLATE_CONTROL = (byte)0x0F;
|
||||
public static final byte TUTORIAL_CONTROL = (byte)0x1F;
|
||||
public static final byte UNPAIR = (byte)0x47;
|
||||
public static final byte UPGRADE_CONTROL = (byte)0x17;
|
||||
public static final byte WEAR_DETECTION_GET = (byte)0x3A;
|
||||
public static final byte WEAR_DETECTION_SET = (byte)0x27;
|
||||
public static final byte UNKNOWN = (byte)0x50;
|
||||
};
|
||||
|
||||
CommandId(byte id) {
|
||||
this.id = id;
|
||||
}
|
||||
public static class DashboardMode {
|
||||
public static final byte FULL = 0x00;
|
||||
public static final byte DUAL = 0x01;
|
||||
public static final byte MINIMAl = 0x02;
|
||||
}
|
||||
|
||||
public static class DashboardConfig {
|
||||
public static final byte SUB_COMMAND_SET_TIME_AND_WEATHER = 0x01;
|
||||
public static final byte SUB_COMMAND_SET_MODE = 0x06;
|
||||
|
||||
public static final byte MODE_FULL = 0x00;
|
||||
public static final byte MODE_DUAL = 0x01;
|
||||
public static final byte MODE_MINIMAl = 0x02;
|
||||
|
||||
public static final byte PANE_NOTES = 0x00;
|
||||
public static final byte PANE_STOCKS = 0x01;
|
||||
public static final byte PANE_NEWS = 0x02;
|
||||
public static final byte PANE_CALENDAR = 0x03;
|
||||
public static final byte PANE_NAVIGATION = 0x04;
|
||||
public static final byte PANE_EMPTY = 0x05;
|
||||
|
||||
public static class DashboardPaneMode {
|
||||
public static final byte QUICK_NOTES = 0x00;
|
||||
public static final byte STOCKS = 0x01;
|
||||
public static final byte NEWS = 0x02;
|
||||
public static final byte CALENDAR = 0x03;
|
||||
public static final byte MAP = 0x04;
|
||||
public static final byte EMPTY = 0x05;
|
||||
}
|
||||
|
||||
public enum SystemSubCommand {
|
||||
RESET((byte) 0x72),
|
||||
GET_FW_INFO((byte) 0x74),
|
||||
SET_DEBUG_LOGGING((byte) 0x6C);
|
||||
public static class DashboardSetSubcommand {
|
||||
public static final byte TIME_AND_WEATHER = 0x01;
|
||||
public static final byte WEATHER = 0x02;
|
||||
public static final byte CALENDAR = 0x03;
|
||||
public static final byte STOCKS = 0x04;
|
||||
public static final byte NEWS = 0x05;
|
||||
public static final byte MODE = 0x06;
|
||||
public static final byte MAP = 0x07;
|
||||
}
|
||||
|
||||
final public byte id;
|
||||
public static class DashboardQuickNoteSubcommand {
|
||||
public static final byte AUDIO_METADATA_GET = 0x01;
|
||||
public static final byte AUDIO_FILE_GET = 0x02;
|
||||
// This one has additional subcommands to delete, update or add a note.
|
||||
public static final byte NOTE_TEXT_EDIT = 0x03;
|
||||
public static final byte AUDIO_FILE_DELETE = 0x04;
|
||||
// Does this delete the metadata?
|
||||
public static final byte AUDIO_RECORD_DELETE = 0x05;
|
||||
// Set or clear a checkmark in front of the entry.
|
||||
public static final byte NOTE_STATUS_EDIT = 0x07;
|
||||
public static final byte NOTE_ADD = 0x08;
|
||||
public static final byte UNKNOWN = 0x09;
|
||||
public static final byte NOTE_STATUS_EDIT_2 = 0x0A;
|
||||
}
|
||||
|
||||
SystemSubCommand(byte id) {
|
||||
this.id = id;
|
||||
}
|
||||
public static class HardwareSubcommand {
|
||||
// public static final byte UNKNOWN = 0x01;
|
||||
public static final byte DISPLAY = 0x02;
|
||||
// public static final byte MIC_MUTEX_RELEASE = 0x03;
|
||||
public static final byte LUM_GEAR = 0x04;
|
||||
public static final byte DOUBLE_TAP_ACTION = 0x05;
|
||||
public static final byte LUM_COEFFICIENT = 0x06;
|
||||
public static final byte LONG_PRESS_ACTION = 0x07;
|
||||
public static final byte HEAD_UP_MIC_ACTIVATION = 0x08;
|
||||
}
|
||||
|
||||
public static class SystemSubcommand {
|
||||
public static final byte DEBUG_LOGGING_SET = 0x6C;
|
||||
public static final byte REBOOT = 0x72;
|
||||
public static final byte FIRMWARE_BUILD_STRING_GET = 0x74;
|
||||
}
|
||||
|
||||
public static final byte SYSTEM_FIRMWARE_BUILD_STRING_PREFIX = 0x6E;
|
||||
|
||||
public static class LanguageId {
|
||||
public static final byte CHINESE = 0x01;
|
||||
public static final byte ENGLISH = 0x02;
|
||||
public static final byte JAPANESE = 0x03;
|
||||
public static final byte KOREAN = 0x04;
|
||||
public static final byte FRENCH = 0x05;
|
||||
public static final byte GERMAN = 0x06;
|
||||
public static final byte SPANISH = 0x07;
|
||||
public static final byte ITALIAN = 0x0E;
|
||||
}
|
||||
|
||||
public static class NavigationSubcommand {
|
||||
public static final byte INIT = 0x00;
|
||||
public static final byte TRIP_STATUS = 0x01;
|
||||
public static final byte MAP_OVERVIEW = 0x02;
|
||||
public static final byte PANORAMIC_MAP = 0x03;
|
||||
public static final byte SYNC = 0x04;
|
||||
public static final byte EXIT = 0x05;
|
||||
public static final byte ARRIVED = 0x06;
|
||||
}
|
||||
|
||||
public static class TextDisplayStyle {
|
||||
// public static final byte UNKNOWN = 0x00;
|
||||
// public static final byte UNKNOWN = 0x10;
|
||||
// public static final byte UNKNOWN = 0x20;
|
||||
public static final byte AI_DISPLAY_AUTO_SCROLL = 0x30;
|
||||
public static final byte AI_DISPLAY_COMPLETE = 0x40;
|
||||
public static final byte AI_DISPLAY_MANUAL_SCROLL = 0x50;
|
||||
public static final byte AI_NETWORK_ERROR = 0x60;
|
||||
public static final byte TEXT_ONY = 0x70;
|
||||
}
|
||||
|
||||
public static class SilentStatus {
|
||||
@@ -188,35 +287,40 @@ public class G1Constants {
|
||||
public static final byte DISABLE = (byte)0x31;
|
||||
}
|
||||
|
||||
public static class DeviceEventId {
|
||||
public static class EventId {
|
||||
// Used to indicate a double tap, but it was used to close the dashboard.
|
||||
public static final byte DOUBLE_TAP_FOR_EXIT = 0x00;
|
||||
public static final byte UNKNOWN_1 = 0x01;
|
||||
public static final byte HEAD_UP = 0x02;
|
||||
public static final byte HEAD_DOWN = 0x03;
|
||||
public static final byte SILENT_MODE_ENABLED = 0x04;
|
||||
public static final byte SILENT_MODE_DISABLED = 0x05;
|
||||
public static final byte GLASSES_WORN = 0x06;
|
||||
public static final byte GLASSES_NOT_WORN_NO_CASE = 0x07;
|
||||
public static final byte CASE_LID_OPEN = 0x08;
|
||||
public static final byte ACTION_DOUBLE_TAP_FOR_EXIT = 0x00;
|
||||
public static final byte ACTION_SINGLE_TAP = 0x01;
|
||||
public static final byte ACTION_HEAD_UP = 0x02;
|
||||
public static final byte ACTION_HEAD_DOWN = 0x03;
|
||||
public static final byte ACTION_SILENT_MODE_ENABLED = 0x04;
|
||||
public static final byte ACTION_SILENT_MODE_DISABLED = 0x05;
|
||||
public static final byte STATE_WORN = 0x06;
|
||||
public static final byte STATE_NOT_WORN_NO_CASE = 0x07;
|
||||
public static final byte STATE_IN_CASE_LID_OPEN = 0x08;
|
||||
// Sent with a payload of 00 or 01 to indicate charging state.
|
||||
public static final byte GLASSES_CHARGING = 0x09;
|
||||
public static final byte STATE_CHARGING = 0x09;
|
||||
// Comes with a payload 00 - 64
|
||||
public static final byte GLASSES_SIDE_BATTERY_LEVEL = 0x0A;
|
||||
public static final byte CASE_LID_CLOSE = 0x0B;
|
||||
public static final byte INFO_BATTERY_LEVEL = 0x0A;
|
||||
public static final byte STATE_IN_CASE_LID_CLOSED = 0x0B;
|
||||
public static final byte UNKNOWN_4 = 0x0C;
|
||||
public static final byte UNKNOWN_5 = 0x0D;
|
||||
// Sent with a payload of 00 or 01 to indicate charging state.
|
||||
public static final byte CASE_CHARGING = 0x0E;
|
||||
public static final byte STATE_CASE_CHARGING = 0x0E;
|
||||
// Comes with a payload 00 - 64
|
||||
public static final byte CASE_BATTERY_LEVEL = 0x0F;
|
||||
public static final byte INFO_CASE_BATTERY_LEVEL = 0x0F;
|
||||
public static final byte UNKNOWN_6 = 0x10;
|
||||
public static final byte BINDING_SUCCESS = 0x11;
|
||||
public static final byte DASHBOARD_SHOW = 0x1E;
|
||||
public static final byte DASHBOARD_CLOSE = 0x1F;
|
||||
public static final byte ACTION_BINDING_SUCCESS = 0x11;
|
||||
// ACTION_LONG_PRESS_HELD will be sent when the long press is started, ACTION_LONG_PRESS and
|
||||
// ACTION_LONG_PRESS_RELEASED will be sent on release.
|
||||
public static final byte ACTION_LONG_PRESS = 0x12;
|
||||
public static final byte ACTION_LONG_PRESS_HELD = 0x17;
|
||||
public static final byte ACTION_LONG_PRESS_RELEASED = 0x18;
|
||||
public static final byte ACTION_DOUBLE_TAP_DASHBOARD_SHOW = 0x1E;
|
||||
public static final byte ACTION_DOUBLE_TAP_DASHBOARD_CLOSE = 0x1F;
|
||||
// Used to initiate translate or transcribe in the official app.
|
||||
// For us it's strictly a double tap that only sends the event.
|
||||
public static final byte DOUBLE_TAP_FOR_ACTION = 0x20;
|
||||
public static final byte ACTION_DOUBLE_TAP = 0x20;
|
||||
}
|
||||
|
||||
public static class TemperatureUnit {
|
||||
|
||||
+307
-98
@@ -38,7 +38,9 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@@ -48,6 +50,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.SettingsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
|
||||
@@ -57,6 +60,9 @@ import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.calendar.CalendarManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
|
||||
|
||||
/**
|
||||
@@ -73,16 +79,21 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
private final HandlerThread backgroundThread = new HandlerThread("even_g1_background_thread", Process.THREAD_PRIORITY_MORE_FAVORABLE);
|
||||
private final Runnable heartBeatRunner;
|
||||
private final Runnable displaySettingsPreviewCloserRunner;
|
||||
private final Runnable calendarSyncRunner;
|
||||
private Handler backgroundTasksHandler = null;
|
||||
private BroadcastReceiver intentReceiver = null;
|
||||
private final Object lensSkewLock = new Object();
|
||||
private final Object initializationLock = new Object();
|
||||
private final Object calendarLock = new Object();
|
||||
private G1SideManager leftSide = null;
|
||||
private G1SideManager rightSide = null;
|
||||
private long lastHeartBeatTime;
|
||||
private long lastHeartBeatDelayTarget;
|
||||
private long heartBeatTargetModifier;
|
||||
private byte globalSequence;
|
||||
|
||||
private List<CalendarEvent> lastSyncedEvents;
|
||||
|
||||
public G1DeviceSupport() {
|
||||
this(LOG);
|
||||
}
|
||||
@@ -98,10 +109,10 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
this.heartBeatRunner = () -> {
|
||||
if (isConnected()) {
|
||||
// We can send any command as a heart beat. The official app uses this one.
|
||||
G1Communications.CommandGetSilentModeSettings leftCommand =
|
||||
new G1Communications.CommandGetSilentModeSettings(b -> { return true;});
|
||||
G1Communications.CommandGetSilentModeSettings rightCommand =
|
||||
new G1Communications.CommandGetSilentModeSettings(b -> { return true;});
|
||||
G1Communications.CommandSilentModeGet leftCommand =
|
||||
new G1Communications.CommandSilentModeGet(b -> { return true;});
|
||||
G1Communications.CommandSilentModeGet rightCommand =
|
||||
new G1Communications.CommandSilentModeGet(b -> { return true;});
|
||||
leftSide.send(leftCommand);
|
||||
rightSide.send(rightCommand);
|
||||
|
||||
@@ -127,25 +138,26 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
|
||||
this.displaySettingsPreviewCloserRunner = () -> {
|
||||
DevicePrefs prefs = getDevicePrefs();
|
||||
G1Communications.CommandSetDisplaySettings command =
|
||||
new G1Communications.CommandSetDisplaySettings(getNextSequence(),
|
||||
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));
|
||||
G1Communications.CommandHardwareDisplaySet command =
|
||||
new G1Communications.CommandHardwareDisplaySet(getNextSequence(),
|
||||
false /* preview */,
|
||||
// Height ranges from 0-8 instead of 1-9, so offset by one to convert from
|
||||
// the slider space.
|
||||
(byte) (prefs.getInt(
|
||||
DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_HEIGHT, 1) - 1),
|
||||
(byte) prefs.getInt(
|
||||
DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_DEPTH, 1));
|
||||
leftSide.send(command);
|
||||
rightSide.send(command);
|
||||
};
|
||||
|
||||
this.calendarSyncRunner = this::syncCalendar;
|
||||
|
||||
// Non Finals
|
||||
this.lastHeartBeatTime = 0;
|
||||
this.lastHeartBeatDelayTarget = G1Constants.HEART_BEAT_TARGET_DELAY_MS;
|
||||
this.globalSequence = 0;
|
||||
this.lastSyncedEvents = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -247,56 +259,59 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
// The final step of each transaction will be to decide if that particular side is the
|
||||
// second side to complete, and if it, that side will be the one responsible for marking
|
||||
// the composite device as initialized.
|
||||
builder.run(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// There is a race condition of each device marking INITIALIZED. If one device
|
||||
// initialize transaction runs after the other has completely finished, the device
|
||||
// will transition from INITIALIZED back to INITIALIZING. Run this final step in a
|
||||
// synchronized block so that only one of the devices can be the final one. Since
|
||||
// this action always runs after "side.initialize(builder)" in the transaction order
|
||||
// it is not possible for both devices to pass this point before both are marked
|
||||
// initialized.
|
||||
// NOTE: side.getConnectingState() != getDevice().getState().
|
||||
synchronized (initializationLock) {
|
||||
// This means that both sides have been connected to and basic info has been collected.
|
||||
if (leftSide != null &&
|
||||
leftSide.getConnectingState() == GBDevice.State.INITIALIZED &&
|
||||
rightSide != null &&
|
||||
rightSide.getConnectingState() == GBDevice.State.INITIALIZED) {
|
||||
// Set device firmware to prevent the following error when data is saved to
|
||||
// the database and device firmware has not been set yet.
|
||||
// java.lang.IllegalArgumentException: the bind value at index 2 is null.
|
||||
// Must be called before the PostInitialize down below.
|
||||
getDevice().setFirmwareVersion("N/A");
|
||||
getDevice().setFirmwareVersion2("N/A");
|
||||
builder.run(() -> {
|
||||
// There is a race condition of each device marking INITIALIZED. If one device
|
||||
// initialize transaction runs after the other has completely finished, the device
|
||||
// will transition from INITIALIZED back to INITIALIZING. Run this final step in a
|
||||
// synchronized block so that only one of the devices can be the final one. Since
|
||||
// this action always runs after "side.initialize(builder)" in the transaction order
|
||||
// it is not possible for both devices to pass this point before both are marked
|
||||
// initialized.
|
||||
// NOTE: side.getConnectingState() != getDevice().getState().
|
||||
synchronized (initializationLock) {
|
||||
// This means that both sides have been connected to and basic info has been collected.
|
||||
if (leftSide != null &&
|
||||
leftSide.getConnectingState() == GBDevice.State.INITIALIZED &&
|
||||
rightSide != null &&
|
||||
rightSide.getConnectingState() == GBDevice.State.INITIALIZED) {
|
||||
// Set device firmware to prevent the following error when data is saved to
|
||||
// the database and device firmware has not been set yet.
|
||||
// java.lang.IllegalArgumentException: the bind value at index 2 is null.
|
||||
// Must be called before the PostInitialize down below.
|
||||
getDevice().setFirmwareVersion("N/A");
|
||||
getDevice().setFirmwareVersion2("N/A");
|
||||
|
||||
// These next steps require that both sides are ready and they can run very
|
||||
// slowly which is why they are done post individual initialization and in
|
||||
// the background. 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(() -> {
|
||||
onSetDashboardMode();
|
||||
onSetTime();
|
||||
// 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.
|
||||
scheduleHeatBeat();
|
||||
// Sent to the left only and it's own transaction, this is a large piece
|
||||
// of data and can cause GB to time out the initialization and get stuck
|
||||
// in a loop.
|
||||
leftSide.send(new G1Communications.CommandSetAppNotificationSettings(
|
||||
leftSide::send, List.of(G1Constants.FIXED_NOTIFICATION_APP_ID),
|
||||
false, false, false));
|
||||
}, 200);
|
||||
// These next steps require that both sides are ready and they can run very
|
||||
// slowly which is why they are done post individual initialization and in
|
||||
// the background. 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(() -> {
|
||||
onSetDashboardMode();
|
||||
onLanguageChange();
|
||||
onSetTime();
|
||||
// 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.
|
||||
scheduleHeatBeat();
|
||||
|
||||
// Mark both sub devices as INITIALIZED so that the composite device is
|
||||
// considered INITIALIZED.
|
||||
getDevice(G1Constants.Side.LEFT.getDeviceIndex())
|
||||
.setUpdateState(GBDevice.State.INITIALIZED, getContext());
|
||||
getDevice(G1Constants.Side.RIGHT.getDeviceIndex())
|
||||
.setUpdateState(GBDevice.State.INITIALIZED, getContext());
|
||||
}
|
||||
// Sent to the left only and it's own transaction, this is a large piece
|
||||
// of data and can cause GB to time out the initialization and get stuck
|
||||
// in a loop.
|
||||
leftSide.send(new G1Communications.CommandNotificationAppListSet(
|
||||
leftSide::send, List.of(G1Constants.FIXED_NOTIFICATION_APP_ID),
|
||||
false, false, false));
|
||||
|
||||
// Tell the calendar events to synchronize.
|
||||
forceNextCalendarSync();
|
||||
syncCalendar();
|
||||
}, 200);
|
||||
|
||||
// Mark both sub devices as INITIALIZED so that the composite device is
|
||||
// considered INITIALIZED.
|
||||
getDevice(G1Constants.Side.LEFT.getDeviceIndex())
|
||||
.setUpdateState(GBDevice.State.INITIALIZED, getContext());
|
||||
getDevice(G1Constants.Side.RIGHT.getDeviceIndex())
|
||||
.setUpdateState(GBDevice.State.INITIALIZED, getContext());
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -305,8 +320,21 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() {
|
||||
super.disconnect();
|
||||
forceNextCalendarSync();
|
||||
|
||||
if (backgroundTasksHandler != null) {
|
||||
// Remove all background tasks.
|
||||
backgroundTasksHandler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
forceNextCalendarSync();
|
||||
|
||||
synchronized (ConnectionMonitor) {
|
||||
if (backgroundTasksHandler != null) {
|
||||
// Remove all background tasks.
|
||||
@@ -397,11 +425,23 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
return globalSequence++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lets a caller reserve multiple sequence all in one go.
|
||||
*/
|
||||
private synchronized byte[] getNextSequence(byte reservedCount) {
|
||||
byte[] out = new byte[reservedCount];
|
||||
for (byte i = 0; i < reservedCount; i++) {
|
||||
out[i] = (byte)(globalSequence + i);
|
||||
}
|
||||
globalSequence += reservedCount;
|
||||
return out;
|
||||
}
|
||||
|
||||
private void scheduleHeatBeat() {
|
||||
Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
long currentMilliseconds = c.getTimeInMillis();
|
||||
long lastDelay = currentMilliseconds - lastHeartBeatTime;
|
||||
LOG.info("{}ms since the last heartbeat", lastDelay);
|
||||
LOG.debug("{}ms since the last heartbeat", lastDelay);
|
||||
|
||||
// The actual delay can change based on the sleep state of the phone CPU, the base delay
|
||||
// should always be enough to keep the glasses connected, however it uses the most amount
|
||||
@@ -412,7 +452,11 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
// rely on the reconnection logic to connect back. After reconnection, the base delay will
|
||||
// be used and the correct system added delay will be determined.
|
||||
long systemAddedTime = lastDelay - lastHeartBeatDelayTarget;
|
||||
long delay = G1Constants.HEART_BEAT_TARGET_DELAY_MS;
|
||||
|
||||
// Anytime we disconnect due to the heartbeat not being fast enough, make the heartbeat more
|
||||
// and more aggressive, so subtract the modifier here.
|
||||
long delay = G1Constants.HEART_BEAT_TARGET_DELAY_MS - heartBeatTargetModifier;
|
||||
LOG.info("{}ms since the last heartbeat, system delay {}ms, modified target {}ms", lastDelay, systemAddedTime, delay);
|
||||
if (systemAddedTime > 0) {
|
||||
delay -= systemAddedTime;
|
||||
}
|
||||
@@ -422,7 +466,7 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
delay = Math.min(delay, G1Constants.HEART_BEAT_TARGET_DELAY_MS);
|
||||
|
||||
backgroundTasksHandler.removeCallbacksAndMessages(heartBeatRunner);
|
||||
LOG.info("Starting heartbeat runner delayed by {}ms", delay);
|
||||
LOG.debug("Starting heartbeat runner delayed by {}ms", delay);
|
||||
backgroundTasksHandler.postDelayed(heartBeatRunner, delay);
|
||||
|
||||
lastHeartBeatTime = currentMilliseconds;
|
||||
@@ -437,13 +481,14 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
backgroundTasksHandler.removeCallbacksAndMessages(displaySettingsPreviewCloserRunner);
|
||||
|
||||
// The glasses expect the setting to be sent with the preview mode set to true.
|
||||
G1Communications.CommandSetDisplaySettings command = new G1Communications.CommandSetDisplaySettings(
|
||||
G1Communications.CommandHardwareDisplaySet
|
||||
command = new G1Communications.CommandHardwareDisplaySet(
|
||||
getNextSequence(),
|
||||
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
|
||||
// Height ranges from 0-8 instead of 1-9, so offset by one to convert from
|
||||
// the slider space.
|
||||
(byte)(prefs.getInt(DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_DEPTH, 0) + 1));
|
||||
(byte)(prefs.getInt(DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_HEIGHT, 1) - 1),
|
||||
(byte)prefs.getInt(DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_DEPTH, 1));
|
||||
|
||||
// Send to both sides.
|
||||
leftSide.send(command);
|
||||
@@ -460,6 +505,93 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
}
|
||||
}
|
||||
|
||||
private void forceNextCalendarSync() {
|
||||
synchronized(calendarLock) {
|
||||
lastSyncedEvents = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void syncCalendar() {
|
||||
if (!getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR, false)) {
|
||||
// Clear the last sync list so that when syncing is re-enabled a sync is forced.
|
||||
lastSyncedEvents = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// Run this on an async thread instead of directly in case was run from the UI thread.
|
||||
backgroundTasksHandler.post(() -> {
|
||||
LOG.info("Even G1 Calendar Sync");
|
||||
List<CalendarEvent> events;
|
||||
synchronized (calendarLock) {
|
||||
// Filter out any events in the past.
|
||||
Calendar now = Calendar.getInstance();
|
||||
CalendarManager
|
||||
calendarManager = new CalendarManager(getContext(), getDevice().getAddress());
|
||||
events = calendarManager.getCalendarEventList()
|
||||
.stream()
|
||||
.filter(e -> e.isAllDay() ||
|
||||
e.getBegin() > (now.getTimeInMillis() - G1Constants.CALENDAR_EVENT_CLEAR_DELAY))
|
||||
.toList();
|
||||
|
||||
// Schedule a sync at the start of the next event so it can be removed from the screen.
|
||||
// If there are no events, just sync again in the future.
|
||||
long nextSyncTime = (events.isEmpty() ? System.currentTimeMillis() : events.get(0).getBegin()) + G1Constants.CALENDAR_EVENT_CLEAR_DELAY;
|
||||
LOG.info("Next Calendar Sync Time: {}", new Date(nextSyncTime));
|
||||
|
||||
// The delay needs to be relative to the current time.
|
||||
long syncDelay = nextSyncTime - System.currentTimeMillis();
|
||||
if (syncDelay < 0) {
|
||||
// If the sync time is in the past, something went wrong, in that case, schedule
|
||||
// the resync for 5 seconds in the future and try again then.
|
||||
syncDelay = System.currentTimeMillis() + 5000;
|
||||
}
|
||||
backgroundTasksHandler.removeCallbacksAndMessages(calendarSyncRunner);
|
||||
backgroundTasksHandler.postDelayed(calendarSyncRunner, syncDelay);
|
||||
|
||||
// The list of events is the same as the last sync, so nothing to do.
|
||||
// Using .equals() checks the contents of the lists, not the actual instance, so a newly
|
||||
// generated list will will be equal.
|
||||
if (events.equals(lastSyncedEvents)) {
|
||||
LOG.info("Skipping Calendar Sync, no new events");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ready sync the current list.
|
||||
lastSyncedEvents = events;
|
||||
}
|
||||
|
||||
boolean use12HourFormat =
|
||||
getDevicePrefs().getTimeFormat()
|
||||
.equals(DeviceSettingsPreferenceConst.PREF_TIMEFORMAT_12H);
|
||||
|
||||
// The same payload is sent to both sides, so only generated it once.
|
||||
byte[] calendarPayload = G1Communications.CommandDashboardCalendarSet.generatePayload(use12HourFormat, events);
|
||||
|
||||
// The sequence ids should be same between the left and right, so reserve them now.
|
||||
byte sequenceCount = G1Communications.CommandDashboardCalendarSet.getRequiredSequenceCount(calendarPayload);
|
||||
byte[] sequenceIds = getNextSequence(sequenceCount);
|
||||
|
||||
// This block is synchronized. We do not want two calls to overlap, otherwise the lenses
|
||||
// could get skewed with different values.
|
||||
synchronized (lensSkewLock) {
|
||||
G1CommandHandler leftCommandHandler =
|
||||
new G1Communications.CommandDashboardCalendarSet(sequenceIds, calendarPayload, leftSide::send);
|
||||
|
||||
G1CommandHandler rightCommandHandler =
|
||||
new G1Communications.CommandDashboardCalendarSet(sequenceIds, calendarPayload, rightSide::send);
|
||||
|
||||
// The commands can be sent in parallel.
|
||||
leftSide.send(leftCommandHandler);
|
||||
rightSide.send(rightCommandHandler);
|
||||
|
||||
if (!leftCommandHandler.waitForResponsePayload() || !rightCommandHandler.waitForResponsePayload()) {
|
||||
LOG.error("Set calendar events on timed out");
|
||||
getDevice().setUpdateState(GBDevice.State.WAITING_FOR_RECONNECT, getContext());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Below are all the onXXX() handlers overridden from the base class. //
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
@@ -507,9 +639,18 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
if (leftDevice != null) leftDevice.setUpdateState(GBDevice.State.WAITING_FOR_RECONNECT, getContext());
|
||||
if (rightDevice != null) rightDevice.setUpdateState(GBDevice.State.WAITING_FOR_RECONNECT, getContext());
|
||||
|
||||
// Reset the last heartbeat time to the epoch so it looks like the last one was a
|
||||
// very long time ago.
|
||||
lastHeartBeatTime = 0;
|
||||
// Anytime we disconnect due to the heartbeat not being fast enough, make the
|
||||
// target modifier to the heartbeat more and more aggressive.
|
||||
Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
||||
long currentMilliseconds = c.getTimeInMillis();
|
||||
long timeSinceLastHeartBeat = currentMilliseconds - lastHeartBeatTime;
|
||||
if (status == /* GATT_CONN_TERMINATE_PEER_USER */ 0x13 && lastHeartBeatDelayTarget < timeSinceLastHeartBeat) {
|
||||
long missedBy = timeSinceLastHeartBeat - lastHeartBeatDelayTarget;
|
||||
heartBeatTargetModifier = Math.min(heartBeatTargetModifier + missedBy,
|
||||
G1Constants.HEART_BEAT_MAX_DELAY_MODIFIER_MS);
|
||||
LOG.info("Heartbeat not fast enough by {}ms, new modifier {}ms", missedBy,
|
||||
heartBeatTargetModifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -532,7 +673,7 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
if (getDevice(G1Constants.Side.LEFT.getDeviceIndex()) != null) {
|
||||
String leftAddress = getDevice(G1Constants.Side.LEFT.getDeviceIndex()).getAddress();
|
||||
if (address.equals(leftAddress) && leftSide != null) {
|
||||
leftSide.send(new G1Communications.CommandSendMtu((byte)Math.min(G1Constants.MTU, mtu)));
|
||||
leftSide.send(new G1Communications.CommandMtuSet((byte)Math.min(G1Constants.MTU, mtu)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,7 +681,7 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
String rightAddress =
|
||||
getDevice(G1Constants.Side.RIGHT.getDeviceIndex()).getAddress();
|
||||
if (address.equals(rightAddress) && rightSide != null) {
|
||||
rightSide.send(new G1Communications.CommandSendMtu((byte)Math.min(G1Constants.MTU, mtu)));
|
||||
rightSide.send(new G1Communications.CommandMtuSet((byte)Math.min(G1Constants.MTU, mtu)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -601,6 +742,28 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
case DeviceSettingsPreferenceConst.PREF_TIMEFORMAT:
|
||||
// Units or time format updated, update the time and weather on the glasses to match
|
||||
onSetTimeOrWeather();
|
||||
// Fall through to update the calendar with the new format.
|
||||
case GBPrefs.CALENDAR_BLACKLIST:
|
||||
case DeviceSettingsPreferenceConst.PREF_CALENDAR_SYNC_EVENTS_AMOUNT:
|
||||
case DeviceSettingsPreferenceConst.PREF_CALENDAR_SYNC_CANCELED:
|
||||
case DeviceSettingsPreferenceConst.PREF_CALENDAR_SYNC_DECLINED:
|
||||
case DeviceSettingsPreferenceConst.PREF_CALENDAR_SYNC_FOCUS_TIME:
|
||||
case DeviceSettingsPreferenceConst.PREF_CALENDAR_SYNC_ALL_DAY:
|
||||
case DeviceSettingsPreferenceConst.PREF_CALENDAR_SYNC_WORKING_LOCATION:
|
||||
case DeviceSettingsPreferenceConst.PREF_CALENDAR_SYNC_COLOR_BLACKLIST:
|
||||
case DeviceSettingsPreferenceConst.PREF_CALENDAR_LOOKAHEAD_DAYS:
|
||||
case DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR:
|
||||
case DeviceSettingsPreferenceConst.PREF_SYNC_BIRTHDAYS:
|
||||
onSetDashboardMode();
|
||||
forceNextCalendarSync();
|
||||
syncCalendar();
|
||||
break;
|
||||
case SettingsActivity.PREF_LANGUAGE:
|
||||
onLanguageChange();
|
||||
// The calendar entries have locale specific parts (eg. day of the week) so sync the
|
||||
// calendar when the language changes.
|
||||
forceNextCalendarSync();
|
||||
syncCalendar();
|
||||
break;
|
||||
default:
|
||||
// Forward to both sides.
|
||||
@@ -616,7 +779,7 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
if (leftSide == null || rightSide == null)
|
||||
return;
|
||||
|
||||
// In FW v1.6.0, they flipped this boolean.
|
||||
// In FW v1.6.0, they flipped this boolean.
|
||||
boolean use12HourFormat =
|
||||
getDevicePrefs().getTimeFormat()
|
||||
.equals(getDevice().getFirmwareVersion().compareTo("1.6.0") >= 0
|
||||
@@ -646,10 +809,10 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
// 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.
|
||||
byte sequence = getNextSequence();
|
||||
G1Communications.CommandHandler leftCommandHandler =
|
||||
new G1Communications.CommandSetTimeAndWeather(sequence, timeMilliseconds,
|
||||
use12HourFormat, weather,
|
||||
useFahrenheit);
|
||||
G1CommandHandler leftCommandHandler =
|
||||
new G1Communications.CommandDashboardWeatherAndTimeSet(sequence, timeMilliseconds,
|
||||
use12HourFormat, weather,
|
||||
useFahrenheit);
|
||||
leftSide.send(leftCommandHandler);
|
||||
if (!leftCommandHandler.waitForResponsePayload()) {
|
||||
LOG.error("Set time on left lens timed out");
|
||||
@@ -657,9 +820,9 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
}
|
||||
|
||||
rightSide.send(
|
||||
new G1Communications.CommandSetTimeAndWeather(sequence, timeMilliseconds,
|
||||
use12HourFormat, weather,
|
||||
useFahrenheit));
|
||||
new G1Communications.CommandDashboardWeatherAndTimeSet(sequence, timeMilliseconds,
|
||||
use12HourFormat, weather,
|
||||
useFahrenheit));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -681,30 +844,29 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
private void onSetDashboardMode() {
|
||||
// Run in the background in case the command hangs and this was run from the UI thread.
|
||||
backgroundTasksHandler.post(() -> {
|
||||
// TODO: Support more than just calendar.
|
||||
boolean showCalendar = getDevicePrefs().getBoolean(DeviceSettingsPreferenceConst.PREF_SYNC_CALENDAR, false);
|
||||
byte mode = showCalendar ? G1Constants.DashboardMode.DUAL : G1Constants.DashboardMode.MINIMAl;
|
||||
byte pane = showCalendar ? G1Constants.DashboardPaneMode.CALENDAR : G1Constants.DashboardPaneMode.EMPTY;
|
||||
|
||||
// This block is synchronized. We do not want two calls to overlap, otherwise the lenses
|
||||
// could get skewed with different values.
|
||||
synchronized (lensSkewLock) {
|
||||
// Send to the left 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.
|
||||
// TODO: Pull these values from the settings and build a UI to configure it.
|
||||
byte sequence = getNextSequence();
|
||||
G1Communications.CommandHandler leftCommandHandler =
|
||||
new G1Communications.CommandSetDashboardModeSettings(
|
||||
sequence,
|
||||
G1Constants.DashboardConfig.MODE_MINIMAl,
|
||||
G1Constants.DashboardConfig.PANE_EMPTY);
|
||||
G1CommandHandler leftCommandHandler =
|
||||
new G1Communications.CommandDashboardModeSet(sequence, mode, pane);
|
||||
|
||||
leftSide.send(leftCommandHandler);
|
||||
if (!leftCommandHandler.waitForResponsePayload()) {
|
||||
LOG.error("Set dashboard on right lens timed out");
|
||||
LOG.error("Set dashboard on left lens timed out");
|
||||
getDevice().setUpdateState(GBDevice.State.WAITING_FOR_RECONNECT, getContext());
|
||||
return;
|
||||
}
|
||||
|
||||
rightSide.send(new G1Communications.CommandSetDashboardModeSettings(
|
||||
sequence,
|
||||
G1Constants.DashboardConfig.MODE_MINIMAl,
|
||||
G1Constants.DashboardConfig.PANE_EMPTY));
|
||||
rightSide.send(new G1Communications.CommandDashboardModeSet(sequence, mode, pane));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -712,8 +874,8 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
@Override
|
||||
public void onReset(int flags) {
|
||||
if (flags == GBDeviceProtocol.RESET_FLAGS_REBOOT) {
|
||||
leftSide.send(new G1Communications.CommandSendReset());
|
||||
rightSide.send(new G1Communications.CommandSendReset());
|
||||
leftSide.send(new G1Communications.CommandSystemRebootControl());
|
||||
rightSide.send(new G1Communications.CommandSystemRebootControl());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,11 +895,58 @@ public class G1DeviceSupport extends AbstractBTLEMultiDeviceSupport {
|
||||
// G1Constants.java for more information.
|
||||
notificationSpec.sourceAppId = G1Constants.FIXED_NOTIFICATION_APP_ID.first;
|
||||
// Notifications are only sent to the left side.
|
||||
leftSide.send(new G1Communications.CommandSendNotification(leftSide::send, notificationSpec));
|
||||
leftSide.send(new G1Communications.CommandNotificationSendControl(leftSide::send, notificationSpec));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteNotification(int id) {
|
||||
leftSide.send(new G1Communications.CommandSendClearNotification(id));
|
||||
leftSide.send(new G1Communications.CommandNotificationClearControl(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) {
|
||||
// Always sync all events.
|
||||
syncCalendar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDeleteCalendarEvent(byte type, long id) {
|
||||
// Always sync all events.
|
||||
syncCalendar();
|
||||
}
|
||||
|
||||
public void onLanguageChange() {
|
||||
String localeString = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getString(SettingsActivity.PREF_LANGUAGE, "auto");
|
||||
if (localeString.equals("auto")) {
|
||||
String language = Locale.getDefault().getLanguage();
|
||||
String country = Locale.getDefault().getCountry();
|
||||
localeString = language + "_" + country.toUpperCase();
|
||||
}
|
||||
|
||||
// If the language is set to one of the supported ones update the glasses to that language.
|
||||
// If the language is not supported, english is used as the default.
|
||||
byte language = switch (localeString.substring(0, 2)) {
|
||||
case "zh" -> G1Constants.LanguageId.CHINESE;
|
||||
case "ja" -> G1Constants.LanguageId.JAPANESE;
|
||||
case "ko" -> G1Constants.LanguageId.KOREAN;
|
||||
case "fr" -> G1Constants.LanguageId.FRENCH;
|
||||
case "de" -> G1Constants.LanguageId.GERMAN;
|
||||
case "es" -> G1Constants.LanguageId.SPANISH;
|
||||
case "it" -> G1Constants.LanguageId.ITALIAN;
|
||||
default -> G1Constants.LanguageId.ENGLISH;
|
||||
};
|
||||
|
||||
byte sequence = getNextSequence();
|
||||
G1CommandHandler leftCommandHandler =
|
||||
new G1Communications.CommandLanguageSet(sequence, language);
|
||||
|
||||
leftSide.send(leftCommandHandler);
|
||||
if (!leftCommandHandler.waitForResponsePayload()) {
|
||||
LOG.error("Set language on left lens timed out");
|
||||
getDevice().setUpdateState(GBDevice.State.WAITING_FOR_RECONNECT, getContext());
|
||||
return;
|
||||
}
|
||||
|
||||
rightSide.send(new G1Communications.CommandLanguageSet(sequence, language));
|
||||
}
|
||||
}
|
||||
|
||||
+54
-52
@@ -63,7 +63,7 @@ public class G1SideManager {
|
||||
private final BiFunction<String, Integer, TransactionBuilder> createTransactionBuilder;
|
||||
private final BluetoothGattCharacteristic rx;
|
||||
private final BluetoothGattCharacteristic tx;
|
||||
private final Set<G1Communications.CommandHandler> commandHandlers;
|
||||
private final Set<G1CommandHandler> commandHandlers;
|
||||
private boolean isSilentModeEnabled;
|
||||
private GBDevice.State connectingState;
|
||||
private boolean debugEnabled;
|
||||
@@ -154,55 +154,55 @@ public class G1SideManager {
|
||||
}
|
||||
|
||||
private void initializeCommon(TransactionBuilder transaction) {
|
||||
sendInTransaction(transaction, new G1Communications.CommandGetBatteryInfo(this::handleBatteryPayload));
|
||||
sendInTransaction(transaction, new G1Communications.CommandGetFirmwareInfo(this::handleFirmwareInfoPayload));
|
||||
sendInTransaction(transaction, new G1Communications.CommandGetSilentModeSettings(this::handleSilentStatusPayload));
|
||||
sendInTransaction(transaction, new G1Communications.CommandInfoBatteryAndFirmwareGet(this::handleBatteryPayload));
|
||||
sendInTransaction(transaction, new G1Communications.CommandSystemFirmwareBuildStringGet(this::handleFirmwareInfoPayload));
|
||||
sendInTransaction(transaction, new G1Communications.CommandSilentModeGet(this::handleSilentStatusPayload));
|
||||
}
|
||||
|
||||
public void initializeLeft(TransactionBuilder transaction) {
|
||||
initializeCommon(transaction);
|
||||
|
||||
// These can be sent to both, but the left lens is used as the master for these settings.
|
||||
sendInTransaction(transaction, new G1Communications.CommandGetBrightnessSettings(this::handleBrightnessSettingsPayload));
|
||||
sendInTransaction(transaction, new G1Communications.CommandGetSerialNumber(this::handleSerialNumberPayload));
|
||||
sendInTransaction(transaction, new G1Communications.CommandBrightnessGet(this::handleBrightnessSettingsPayload));
|
||||
sendInTransaction(transaction, new G1Communications.CommandInfoSerialNumberGlassesGet(this::handleSerialNumberPayload));
|
||||
}
|
||||
|
||||
public void initializeRight(TransactionBuilder transaction) {
|
||||
initializeCommon(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));
|
||||
sendInTransaction(transaction, new G1Communications.CommandHeadUpAngleGet(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.CommandGetDisplaySettings(this::handleDisplaySettingsPayload));
|
||||
sendInTransaction(transaction, new G1Communications.CommandGetWearDetectionSettings(this::handleWearDetectionSettingsPayload));
|
||||
sendInTransaction(transaction, new G1Communications.CommandGetNotificationDisplaySettings(this::handleNotificationDisplaySettingsPayload));
|
||||
sendInTransaction(transaction, new G1Communications.CommandHardwareDisplayGet(this::handleDisplaySettingsPayload));
|
||||
sendInTransaction(transaction, new G1Communications.CommandWearDetectionGet(this::handleWearDetectionSettingsPayload));
|
||||
sendInTransaction(transaction, new G1Communications.CommandNotificationAutoDisplayGet(this::handleNotificationDisplaySettingsPayload));
|
||||
}
|
||||
|
||||
public void onSendConfiguration(String config) {
|
||||
DevicePrefs prefs = getDevicePrefs();
|
||||
switch (config) {
|
||||
case DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_ACTIVATION_ANGLE:
|
||||
send(new G1Communications.CommandSetHeadGestureSettings(
|
||||
send(new G1Communications.CommandHeadUpAngleSet(
|
||||
(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(
|
||||
send(new G1Communications.CommandBrightnessSet(
|
||||
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(
|
||||
send(new G1Communications.CommandWearDetectionSet(
|
||||
prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_WEAR_SENSOR_TOGGLE, true)));
|
||||
break;
|
||||
case DeviceSettingsPreferenceConst.PREF_DEVICE_LOGS_TOGGLE:
|
||||
this.debugEnabled = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_DEVICE_LOGS_TOGGLE, false);
|
||||
send(new G1Communications.CommandSetDebugLogSettings(this.debugEnabled));
|
||||
send(new G1Communications.CommandSystemDebugLoggingSet(this.debugEnabled));
|
||||
break;
|
||||
case DeviceSettingsPreferenceConst.PREF_SCREEN_ON_ON_NOTIFICATIONS:
|
||||
case DeviceSettingsPreferenceConst.PREF_SCREEN_ON_ON_NOTIFICATIONS_TIMEOUT:
|
||||
send(new G1Communications.CommandSetNotificationDisplaySettings(
|
||||
send(new G1Communications.CommandNotificationAutoDisplaySet(
|
||||
prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SCREEN_ON_ON_NOTIFICATIONS, true),
|
||||
(byte)prefs.getInt(DeviceSettingsPreferenceConst.PREF_SCREEN_ON_ON_NOTIFICATIONS_TIMEOUT, 5)));
|
||||
break;
|
||||
@@ -211,18 +211,18 @@ public class G1SideManager {
|
||||
|
||||
public void onToggleSilentMode() {
|
||||
isSilentModeEnabled = !isSilentModeEnabled;
|
||||
send(new G1Communications.CommandSetSilentModeSettings(isSilentModeEnabled));
|
||||
send(new G1Communications.CommandSilentModeSet(isSilentModeEnabled));
|
||||
}
|
||||
|
||||
public void send(G1Communications.CommandHandler command) {
|
||||
public void send(G1CommandHandler command) {
|
||||
TransactionBuilder transaction =
|
||||
createTransactionBuilder.apply(command.getName(), mySide.getDeviceIndex());
|
||||
createTransactionBuilder.apply(command.getName() + "_" + mySide.getName(), mySide.getDeviceIndex());
|
||||
sendInTransaction(transaction, command);
|
||||
transaction.queue();
|
||||
}
|
||||
|
||||
private void sendInTransaction(TransactionBuilder transaction, G1Communications.CommandHandler command) {
|
||||
LOG.debug("Send command {} on side {}", command.getName(), mySide.getDeviceIndex());
|
||||
private void sendInTransaction(TransactionBuilder transaction, G1CommandHandler command) {
|
||||
LOG.debug("Send command {} on side {}", command.getName(), mySide.getName());
|
||||
|
||||
// Write the packet to the BLE txn.
|
||||
transaction.write(tx, command.serialize());
|
||||
@@ -247,7 +247,7 @@ public class G1SideManager {
|
||||
// the lock.
|
||||
if (retry) {
|
||||
LOG.debug("Retry {} command {} on side {}", command.getRetryCount(),
|
||||
command.getName(), mySide.getDeviceIndex());
|
||||
command.getName(), mySide.getName());
|
||||
// TODO: This will change the global sequence number of the command, is this
|
||||
// what the stock app does on retry? Or does it resend with the same one.
|
||||
send(command);
|
||||
@@ -256,7 +256,7 @@ public class G1SideManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void registerResponseHandler(G1Communications.CommandHandler commandHandler) {
|
||||
private void registerResponseHandler(G1CommandHandler commandHandler) {
|
||||
synchronized (commandHandlers) {
|
||||
commandHandlers.add(commandHandler);
|
||||
}
|
||||
@@ -279,10 +279,10 @@ public class G1SideManager {
|
||||
}
|
||||
|
||||
public boolean handlePayload(byte[] payload) {
|
||||
for (G1Communications.CommandHandler commandHandler : commandHandlers) {
|
||||
for (G1CommandHandler commandHandler : commandHandlers) {
|
||||
if (commandHandler.responseMatches(payload)) {
|
||||
LOG.debug("Got response payload for command {} on side {}: {}",
|
||||
commandHandler.getName(), mySide.getDeviceIndex(),
|
||||
commandHandler.getName(), mySide.getName(),
|
||||
Logging.formatBytes(payload));
|
||||
synchronized (commandHandlers) {
|
||||
commandHandlers.remove(commandHandler);
|
||||
@@ -296,23 +296,24 @@ public class G1SideManager {
|
||||
|
||||
// The glasses will send unprompted messages indicating certain events happening.
|
||||
// ex. glasses are taken off, glasses are charging, or touch pad was pressed.
|
||||
if (G1Communications.DeviceEvent.messageMatches(payload)) {
|
||||
if (G1Communications.MessageEvent.messageMatches(payload)) {
|
||||
return handleDeviceEventPayload(payload);
|
||||
}
|
||||
|
||||
if (G1Communications.DebugLog.messageMatches(payload)) {
|
||||
if (G1Communications.MessageDebug.messageMatches(payload)) {
|
||||
return handleDebugLogPayload(payload);
|
||||
}
|
||||
|
||||
LOG.debug("Unhandled payload on side {}: {}",
|
||||
mySide.getDeviceIndex(), Logging.formatBytes(payload));
|
||||
mySide.getName(), Logging.formatBytes(payload));
|
||||
|
||||
// Not handled by any handlers.
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean handleBatteryPayload(byte[] payload) {
|
||||
updateBatteryLevel(G1Communications.CommandGetBatteryInfo.getBatteryPercent(payload));
|
||||
updateBatteryLevel(
|
||||
G1Communications.CommandInfoBatteryAndFirmwareGet.getBatteryPercent(payload));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -335,11 +336,11 @@ public class G1SideManager {
|
||||
}
|
||||
|
||||
private boolean handleSerialNumberPayload(byte[] payload) {
|
||||
String serialNumber = G1Communications.CommandGetSerialNumber.getSerialNumber(payload);
|
||||
String serialNumber = G1Communications.CommandInfoSerialNumberGlassesGet.getSerialNumber(payload);
|
||||
|
||||
// Parse the hardware information out of the serial number.
|
||||
int shape = G1Communications.CommandGetSerialNumber.getFrameType(payload);
|
||||
int color = G1Communications.CommandGetSerialNumber.getFrameColor(payload);
|
||||
int shape = G1Communications.CommandInfoSerialNumberGlassesGet.getFrameType(payload);
|
||||
int color = G1Communications.CommandInfoSerialNumberGlassesGet.getFrameColor(payload);
|
||||
if (shape != -1 && color != -1) {
|
||||
GBDeviceEventVersionInfo fwInfo = new GBDeviceEventVersionInfo();
|
||||
fwInfo.hwVersion = GBApplication.getContext().getString(
|
||||
@@ -357,16 +358,16 @@ public class G1SideManager {
|
||||
}
|
||||
|
||||
private boolean handleSilentStatusPayload(byte[] payload) {
|
||||
isSilentModeEnabled = G1Communications.CommandGetSilentModeSettings.isEnabled(payload);
|
||||
isSilentModeEnabled = G1Communications.CommandSilentModeGet.isEnabled(payload);
|
||||
return true;
|
||||
}
|
||||
private boolean handleDisplaySettingsPayload(byte[] payload) {
|
||||
GBDeviceEventUpdatePreferences prefsEvent = new GBDeviceEventUpdatePreferences();
|
||||
prefsEvent.preferences.put(DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_HEIGHT,
|
||||
G1Communications.CommandGetDisplaySettings.getHeight(payload));
|
||||
G1Communications.CommandHardwareDisplayGet.getHeight(payload));
|
||||
// Depth is indexed is 1-9, so subtract 1 to map it to the 0-8 of the slider.
|
||||
prefsEvent.preferences.put(DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_DEPTH,
|
||||
G1Communications.CommandGetDisplaySettings.getDepth(payload) - 1);
|
||||
G1Communications.CommandHardwareDisplayGet.getDepth(payload) - 1);
|
||||
evaluateGBDeviceEvent(prefsEvent);
|
||||
return true;
|
||||
}
|
||||
@@ -375,7 +376,7 @@ public class G1SideManager {
|
||||
GBDeviceEventUpdatePreferences prefsEvent = new GBDeviceEventUpdatePreferences();
|
||||
prefsEvent.preferences.put(
|
||||
DeviceSettingsPreferenceConst.PREF_EVEN_REALITIES_SCREEN_ACTIVATION_ANGLE,
|
||||
G1Communications.CommandGetHeadGestureSettings.getActivationAngle(payload));
|
||||
G1Communications.CommandHeadUpAngleGet.getActivationAngle(payload));
|
||||
evaluateGBDeviceEvent(prefsEvent);
|
||||
return true;
|
||||
}
|
||||
@@ -383,9 +384,9 @@ public class G1SideManager {
|
||||
private boolean handleBrightnessSettingsPayload(byte[] payload) {
|
||||
GBDeviceEventUpdatePreferences prefsEvent = new GBDeviceEventUpdatePreferences();
|
||||
prefsEvent.preferences.put(DeviceSettingsPreferenceConst.PREF_SCREEN_AUTO_BRIGHTNESS,
|
||||
G1Communications.CommandGetBrightnessSettings.isAutoBrightnessEnabled(payload));
|
||||
G1Communications.CommandBrightnessGet.isAutoBrightnessEnabled(payload));
|
||||
prefsEvent.preferences.put(DeviceSettingsPreferenceConst.PREF_SCREEN_BRIGHTNESS,
|
||||
G1Communications.CommandGetBrightnessSettings.getBrightnessLevel(payload));
|
||||
G1Communications.CommandBrightnessGet.getBrightnessLevel(payload));
|
||||
evaluateGBDeviceEvent(prefsEvent);
|
||||
return true;
|
||||
}
|
||||
@@ -393,7 +394,7 @@ public class G1SideManager {
|
||||
private boolean handleWearDetectionSettingsPayload(byte[] payload) {
|
||||
GBDeviceEventUpdatePreferences prefsEvent = new GBDeviceEventUpdatePreferences();
|
||||
prefsEvent.preferences.put(DeviceSettingsPreferenceConst.PREF_WEAR_SENSOR_TOGGLE,
|
||||
G1Communications.CommandGetWearDetectionSettings.isEnabled(payload));
|
||||
G1Communications.CommandWearDetectionGet.isEnabled(payload));
|
||||
evaluateGBDeviceEvent(prefsEvent);
|
||||
return true;
|
||||
}
|
||||
@@ -401,40 +402,41 @@ public class G1SideManager {
|
||||
private boolean handleNotificationDisplaySettingsPayload(byte[] payload) {
|
||||
GBDeviceEventUpdatePreferences prefsEvent = new GBDeviceEventUpdatePreferences();
|
||||
prefsEvent.preferences.put(DeviceSettingsPreferenceConst.PREF_SCREEN_ON_ON_NOTIFICATIONS,
|
||||
G1Communications.CommandGetNotificationDisplaySettings.isEnabled(payload));
|
||||
G1Communications.CommandNotificationAutoDisplayGet.isEnabled(payload));
|
||||
prefsEvent.preferences.put(
|
||||
DeviceSettingsPreferenceConst.PREF_SCREEN_ON_ON_NOTIFICATIONS_TIMEOUT,
|
||||
Integer.toString(G1Communications.CommandGetNotificationDisplaySettings.getTimeout(payload)));
|
||||
Integer.toString(
|
||||
G1Communications.CommandNotificationAutoDisplayGet.getTimeout(payload)));
|
||||
evaluateGBDeviceEvent(prefsEvent);
|
||||
return true;
|
||||
}
|
||||
private boolean handleDeviceEventPayload(byte[] payload) {
|
||||
switch (G1Communications.DeviceEvent.getEventId(payload)) {
|
||||
case G1Constants.DeviceEventId.GLASSES_CHARGING:
|
||||
switch (G1Communications.MessageEvent.getEventId(payload)) {
|
||||
case G1Constants.EventId.STATE_CHARGING:
|
||||
updateBatteryState(
|
||||
G1Communications.DeviceEvent.getValue(payload) == 0x01
|
||||
G1Communications.MessageEvent.getValue(payload) == 0x01
|
||||
? BatteryState.BATTERY_CHARGING
|
||||
: BatteryState.BATTERY_NORMAL);
|
||||
break;
|
||||
case G1Constants.DeviceEventId.GLASSES_SIDE_BATTERY_LEVEL:
|
||||
updateBatteryLevel(G1Communications.DeviceEvent.getValue(payload));
|
||||
case G1Constants.EventId.INFO_BATTERY_LEVEL:
|
||||
updateBatteryLevel(G1Communications.MessageEvent.getValue(payload));
|
||||
break;
|
||||
case G1Constants.DeviceEventId.CASE_CHARGING:
|
||||
case G1Constants.EventId.STATE_CASE_CHARGING:
|
||||
updateBatteryState(
|
||||
G1Communications.DeviceEvent.getValue(payload) == 0x01
|
||||
G1Communications.MessageEvent.getValue(payload) == 0x01
|
||||
? BatteryState.BATTERY_CHARGING
|
||||
: BatteryState.BATTERY_NORMAL,
|
||||
G1Constants.CASE_BATTERY_INDEX);
|
||||
break;
|
||||
case G1Constants.DeviceEventId.CASE_BATTERY_LEVEL:
|
||||
updateBatteryLevel(G1Communications.DeviceEvent.getValue(payload),
|
||||
case G1Constants.EventId.INFO_CASE_BATTERY_LEVEL:
|
||||
updateBatteryLevel(G1Communications.MessageEvent.getValue(payload),
|
||||
G1Constants.CASE_BATTERY_INDEX);
|
||||
break;
|
||||
case G1Constants.DeviceEventId.GLASSES_NOT_WORN_NO_CASE:
|
||||
case G1Constants.EventId.STATE_NOT_WORN_NO_CASE:
|
||||
updateBatteryState(BatteryState.NO_BATTERY, G1Constants.CASE_BATTERY_INDEX);
|
||||
break;
|
||||
default:
|
||||
LOG.debug("Device Event on side {}: {}", mySide.getDeviceIndex(),
|
||||
LOG.debug("Device Event on side {}: {}", mySide.getName(),
|
||||
Logging.formatBytes(payload));
|
||||
return false;
|
||||
}
|
||||
@@ -449,7 +451,7 @@ public class G1SideManager {
|
||||
getDevicePrefs().getPreferences().edit().putBoolean(
|
||||
DeviceSettingsPreferenceConst.PREF_DEVICE_LOGS_TOGGLE, this.debugEnabled).apply();
|
||||
}
|
||||
LOG.info("{}: {}", mySide, G1Communications.DebugLog.getMessage(payload));
|
||||
LOG.info("{}: {}", mySide, G1Communications.MessageDebug.getMessage(payload));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,20 +4,20 @@
|
||||
android:tint="?attr/colorControlNormal"
|
||||
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="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"
|
||||
@@ -25,7 +25,6 @@
|
||||
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"
|
||||
@@ -33,14 +32,11 @@
|
||||
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" />
|
||||
|
||||
|
||||
<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,30 @@
|
||||
<vector android:height="24dp" android:tint="?attr/colorControlNormal"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- Side Lines -->
|
||||
<path
|
||||
android:strokeWidth="2"
|
||||
android:pathData="
|
||||
M3,4v16
|
||||
M21,4v16"
|
||||
android:strokeColor="#000000"/>
|
||||
|
||||
<!-- Horizontal Lines -->
|
||||
<path
|
||||
android:strokeWidth="4"
|
||||
android:strokeLineCap="round"
|
||||
android:pathData="
|
||||
M4,4h16
|
||||
M4,20h16"
|
||||
android:strokeColor="#000000"/>
|
||||
|
||||
<!-- Screen Lines -->
|
||||
<path
|
||||
android:strokeWidth="1.5"
|
||||
android:pathData="
|
||||
M6,9h10
|
||||
M6,12h7
|
||||
M6,15h9"
|
||||
android:strokeColor="#000000"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,78 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
|
||||
<!--Head-->
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="
|
||||
M360,520
|
||||
Q294,520 247,473
|
||||
Q200,426 200,360
|
||||
Q200,294 247,247
|
||||
Q294,200 360,200
|
||||
Q426,200 473,247
|
||||
Q520,294 520,360
|
||||
Q520,426 473,473
|
||||
Q426,520 360,520
|
||||
Z
|
||||
M360,440
|
||||
Q393,440 416.5,416.5
|
||||
Q440,393 440,360
|
||||
Q440,327 416.5,303.5
|
||||
Q393,280 360,280
|
||||
Q327,280 303.5,303.5
|
||||
Q280,327 280,360
|
||||
Q280,393 303.5,416.5
|
||||
Q327,440 360,440
|
||||
Z"/>
|
||||
|
||||
<!--Body-->
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="
|
||||
M40,840L40,728
|
||||
Q40,695 57,666
|
||||
Q74,637 104,622
|
||||
Q155,596 219,578
|
||||
Q283,560 360,560
|
||||
Q437,560 501,578
|
||||
Q565,596 616,622
|
||||
Q646,637 663,666
|
||||
Q680,695 680,728
|
||||
L680,840
|
||||
Z
|
||||
|
||||
M120,760L600,760
|
||||
L600,728
|
||||
Q600,717 594.5,708
|
||||
Q589,699 580,694
|
||||
Q544,676 487.5,658
|
||||
Q431,640 360,640
|
||||
Q289,640 232.5,658
|
||||
Q176,676 140,694
|
||||
Q131,699 125.5,708
|
||||
Q120,717 120,728
|
||||
Z"/>
|
||||
|
||||
<!--Arrow arc -->
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:pathData="M700,120 A400,300 0 0,1 670,420"
|
||||
android:strokeWidth="80"
|
||||
android:strokeLineCap="round"
|
||||
android:strokeLineJoin="round"/>
|
||||
|
||||
<!--Arrow head -->
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:pathData="M795,135 L670,110 L645,210"
|
||||
android:strokeLineJoin="round"
|
||||
android:strokeWidth="80"
|
||||
android:strokeLineCap="round"/>
|
||||
</vector>
|
||||
@@ -4,7 +4,20 @@
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<!-- Arc -->
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:pathData="M205,285 A140,140 0 1,1 470,285"
|
||||
android:strokeWidth="80"
|
||||
/>
|
||||
|
||||
<!-- Hand -->
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M419,880Q391,880 366.5,868Q342,856 325,834L107,557L126,537Q146,516 174,512Q202,508 226,523L300,568L300,240Q300,223 311.5,211.5Q323,200 340,200Q357,200 369,211.5Q381,223 381,240L381,712L284,652L388,785Q394,792 402,796Q410,800 419,800L640,800Q673,800 696.5,776.5Q720,753 720,720L720,560Q720,543 708.5,531.5Q697,520 680,520L461,520L461,440L680,440Q730,440 765,475Q800,510 800,560L800,720Q800,786 753,833Q706,880 640,880L419,880ZM167,340Q154,318 147,292.5Q140,267 140,240Q140,157 198.5,98.5Q257,40 340,40Q423,40 481.5,98.5Q540,157 540,240Q540,267 533,292.5Q526,318 513,340L444,300Q452,286 456,271.5Q460,257 460,240Q460,190 425,155Q390,120 340,120Q290,120 255,155Q220,190 220,240Q220,257 224,271.5Q228,286 236,300L167,340ZM502,620L502,620L502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620L502,620Z"/>
|
||||
android:pathData=
|
||||
"M419,880Q391,880 366.5,868Q342,856 325,834L107,557L126,537Q146,516 174,512Q202,508 226,523L300,568L300,240Q300,223 311.5,211.5Q323,200 340,200Q357,200 369,211.5Q381,223 381,240
|
||||
L381,712L284,652L388,785Q394,792 402,796Q410,800 419,800L640,800Q673,800 696.5,776.5Q720,753 720,720L720,560Q720,543 708.5,531.5Q697,520 680,520
|
||||
L461,520L461,440L680,440Q730,440 765,475Q800,510 800,560L800,720Q800,786 753,833Q706,880 640,880L419,880Z"
|
||||
/>
|
||||
</vector>
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
|
||||
<!-- Arcs -->
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:pathData="M245,347 A140,140 0 1,1 430,347"
|
||||
android:strokeWidth="50"
|
||||
/>
|
||||
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:pathData="M195,397 A210,210 0 1,1 480,397"
|
||||
android:strokeWidth="50"
|
||||
/>
|
||||
|
||||
<!-- Hand -->
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData=
|
||||
"M419,880Q391,880 366.5,868Q342,856 325,834L107,557L126,537Q146,516 174,512Q202,508 226,523L300,568L300,240Q300,223 311.5,211.5Q323,200 340,200Q357,200 369,211.5Q381,223 381,240
|
||||
L381,712L284,652L388,785Q394,792 402,796Q410,800 419,800L640,800Q673,800 696.5,776.5Q720,753 720,720L720,560Q720,543 708.5,531.5Q697,520 680,520
|
||||
L461,520L461,440L680,440Q730,440 765,475Q800,510 800,560L800,720Q800,786 753,833Q706,880 640,880L419,880Z"
|
||||
/>
|
||||
</vector>
|
||||
@@ -0,0 +1,35 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
|
||||
<group
|
||||
android:scaleX="-1"
|
||||
android:translateX="960">
|
||||
|
||||
<!-- Arcs -->
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:pathData="M245,347 A140,140 0 1,1 430,347"
|
||||
android:strokeWidth="50"
|
||||
/>
|
||||
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:pathData="M195,397 A210,210 0 1,1 480,397"
|
||||
android:strokeWidth="50"
|
||||
/>
|
||||
|
||||
<!-- Hand -->
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="
|
||||
M419,880Q391,880 366.5,868Q342,856 325,834L107,557L126,537Q146,516 174,512Q202,508 226,523L300,568L300,240Q300,223 311.5,211.5Q323,200 340,200Q357,200 369,211.5Q381,223 381,240
|
||||
L381,712L284,652L388,785Q394,792 402,796Q410,800 419,800L640,800Q673,800 696.5,776.5Q720,753 720,720L720,560Q720,543 708.5,531.5Q697,520 680,520
|
||||
L461,520L461,440L680,440Q730,440 765,475Q800,510 800,560L800,720Q800,786 753,833Q706,880 640,880L419,880Z" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,28 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
|
||||
<group
|
||||
android:scaleX="-1"
|
||||
android:translateX="960">
|
||||
|
||||
<!-- Arc -->
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:pathData="M205,285 A140,140 0 1,1 470,285"
|
||||
android:strokeWidth="80"
|
||||
/>
|
||||
|
||||
<!-- Hand -->
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="
|
||||
M419,880Q391,880 366.5,868Q342,856 325,834L107,557L126,537Q146,516 174,512Q202,508 226,523L300,568L300,240Q300,223 311.5,211.5Q323,200 340,200Q357,200 369,211.5Q381,223 381,240
|
||||
L381,712L284,652L388,785Q394,792 402,796Q410,800 419,800L640,800Q673,800 696.5,776.5Q720,753 720,720L720,560Q720,543 708.5,531.5Q697,520 680,520
|
||||
L461,520L461,440L680,440Q730,440 765,475Q800,510 800,560L800,720Q800,786 753,833Q706,880 640,880L419,880Z"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,22 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<!-- Arc -->
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:pathData="M245,347 A140,140 0 1,1 430,347"
|
||||
android:strokeWidth="80"
|
||||
/>
|
||||
|
||||
<!-- Hand -->
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData=
|
||||
"M419,880Q391,880 366.5,868Q342,856 325,834L107,557L126,537Q146,516 174,512Q202,508 226,523L300,568L300,240Q300,223 311.5,211.5Q323,200 340,200Q357,200 369,211.5Q381,223 381,240
|
||||
L381,712L284,652L388,785Q394,792 402,796Q410,800 419,800L640,800Q673,800 696.5,776.5Q720,753 720,720L720,560Q720,543 708.5,531.5Q697,520 680,520
|
||||
L461,520L461,440L680,440Q730,440 765,475Q800,510 800,560L800,720Q800,786 753,833Q706,880 640,880L419,880Z"
|
||||
/></vector>
|
||||
@@ -0,0 +1,28 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
|
||||
<group
|
||||
android:scaleX="-1"
|
||||
android:translateX="960">
|
||||
|
||||
<!-- Arc -->
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:strokeColor="#FFFFFFFF"
|
||||
android:pathData="M245,347 A140,140 0 1,1 430,347"
|
||||
android:strokeWidth="80"
|
||||
/>
|
||||
|
||||
<!-- Hand -->
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="
|
||||
M419,880Q391,880 366.5,868Q342,856 325,834L107,557L126,537Q146,516 174,512Q202,508 226,523L300,568L300,240Q300,223 311.5,211.5Q323,200 340,200Q357,200 369,211.5Q381,223 381,240
|
||||
L381,712L284,652L388,785Q394,792 402,796Q410,800 419,800L640,800Q673,800 696.5,776.5Q720,753 720,720L720,560Q720,543 708.5,531.5Q697,520 680,520
|
||||
L461,520L461,440L680,440Q730,440 765,475Q800,510 800,560L800,720Q800,786 753,833Q706,880 640,880L419,880Z"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -5343,6 +5343,14 @@
|
||||
<item>never</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_device_even_realities_g1_double_tap">
|
||||
<item>@string/menuitem_nothing</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="pref_device_even_realities_g1_long_press">
|
||||
<item>@string/menuitem_nothing</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="health_connect_initial_sync_periods">
|
||||
<item>@string/health_connect_initial_sync_3_days</item>
|
||||
<item>@string/health_connect_initial_sync_7_days</item>
|
||||
|
||||
@@ -211,6 +211,7 @@
|
||||
<string name="pref_header_audio">Audio</string>
|
||||
<string name="pref_header_calendar">Calendar</string>
|
||||
<string name="pref_header_connection">Connection</string>
|
||||
<string name="pref_header_dashboard">Dashboard</string>
|
||||
<string name="pref_header_display">Display</string>
|
||||
<string name="pref_header_generic">Generic</string>
|
||||
<string name="pref_header_health">Health</string>
|
||||
@@ -4291,10 +4292,20 @@
|
||||
<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_enable_title">Show on Head Up</string>
|
||||
<string name="pref_even_realities_g1_screen_activation_angle_enable_summary">Enable auto showing the dashboard when the wearer looks up</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>
|
||||
<string name="pref_even_realities_g1_touch_double_left_title">Double tap left</string>
|
||||
<string name="pref_even_realities_g1_touch_double_left_summary">When the left touch pad is double tapped, take this action: %s</string>
|
||||
<string name="pref_even_realities_g1_touch_double_right_title">Double tap right</string>
|
||||
<string name="pref_even_realities_g1_touch_double_right_summary">When the right pad is double tapped, take this action: %s</string>
|
||||
<string name="pref_even_realities_g1_touch_long_left_title">Long press left</string>
|
||||
<string name="pref_even_realities_g1_touch_long_left_summary">When the left touch pad is long pressed, take this action: %s</string>
|
||||
<string name="pref_even_realities_g1_touch_long_right_title">Long press right</string>
|
||||
<string name="pref_even_realities_g1_touch_long_right_summary">When the right pad is long pressed, take this action: %s</string>
|
||||
<string name="even_realities_frame_shape_G1A">G1A/Round</string>
|
||||
<string name="even_realities_frame_shape_G1B">G1B/Square</string>
|
||||
<string name="even_realities_frame_color_grey">Grey</string>
|
||||
@@ -4303,6 +4314,7 @@
|
||||
<string name="even_realities_frame_description">%1$s %2$s (%3$s: %4$s)</string>
|
||||
<string name="even_realities_left_lens">Left Lens</string>
|
||||
<string name="even_realities_right_lens">Right Lens</string>
|
||||
<string name="even_realities_no_calendar_events">No upcoming events</string>
|
||||
<string name="pref_aawireless_dongle_confirmation">After toggling dongle mode, you will need to pair the device again. Please wait at least 30 seconds for the setting to be applied.</string>
|
||||
<string name="switch_to_next_phone">Next phone</string>
|
||||
<string name="toggle_standby_on_off">Standby on/off</string>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- <PreferenceCategory android:title="@string/pref_header_display">-->
|
||||
<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>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- TODO: There is a setting to enable and disable this. Figure out how it works.-->
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:enabled="false"
|
||||
android:disableDependentsState="false"
|
||||
android:icon="@drawable/ic_head_up"
|
||||
android:key="pref_even_realities_g1_screen_activation_angle_enable"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:summary="@string/pref_even_realities_g1_screen_activation_angle_enable_summary"
|
||||
android:title="@string/pref_even_realities_g1_screen_activation_angle_enable_title" />
|
||||
<!-- android:dependency="pref_even_realities_g1_screen_activation_angle_enable"-->
|
||||
<SeekBarPreference
|
||||
android:defaultValue="40"
|
||||
android:icon="@drawable/ic_angle"
|
||||
android:key="pref_even_realities_g1_screen_activation_angle"
|
||||
android:max="60"
|
||||
android:summary="@string/pref_even_realities_g1_screen_activation_angle_summary"
|
||||
android:title="@string/pref_even_realities_g1_screen_activation_angle_title"
|
||||
app:showSeekBarValue="true" />
|
||||
<Preference
|
||||
android:defaultValue="0"
|
||||
android:dependency="pref_even_realities_g1_screen_activation_angle_enable"
|
||||
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" />
|
||||
<!-- </PreferenceCategory>-->
|
||||
</PreferenceScreen>
|
||||
@@ -1,56 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
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" />
|
||||
|
||||
<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" />
|
||||
<SeekBarPreference
|
||||
android:icon="@drawable/ic_arrows_up_down"
|
||||
android:key="pref_even_realities_g1_screen_height"
|
||||
android:max="9"
|
||||
android:min="1"
|
||||
app:min="1"
|
||||
android:defaultValue="1"
|
||||
android:summary="@string/pref_even_realities_g1_screen_location_summary"
|
||||
android:title="@string/pref_even_realities_g1_screen_location_title"
|
||||
app:showSeekBarValue="true" />
|
||||
<SeekBarPreference
|
||||
android:icon="@drawable/ic_arrow_depth"
|
||||
android:key="pref_even_realities_g1_screen_depth"
|
||||
android:max="9"
|
||||
android:min="1"
|
||||
app:min="1"
|
||||
android:defaultValue="1"
|
||||
android:summary="@string/pref_even_realities_g1_screen_depth_summary"
|
||||
android:title="@string/pref_even_realities_g1_screen_depth_title"
|
||||
app:showSeekBarValue="true" />
|
||||
<!-- </PreferenceCategory>-->
|
||||
</PreferenceScreen>
|
||||
</PreferenceScreen>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<ListPreference
|
||||
android:title="@string/pref_even_realities_g1_touch_double_left_title"
|
||||
android:summary="@string/pref_even_realities_g1_touch_double_left_summary"
|
||||
android:defaultValue="@string/menuitem_nothing"
|
||||
android:entries="@array/pref_device_even_realities_g1_double_tap"
|
||||
android:entryValues="@array/pref_device_even_realities_g1_double_tap"
|
||||
android:key="pref_even_realities_g1_touch_double_left"
|
||||
|
||||
android:icon="@drawable/ic_touch_double_left"
|
||||
/>
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="@string/menuitem_nothing"
|
||||
android:entries="@array/pref_device_even_realities_g1_double_tap"
|
||||
android:entryValues="@array/pref_device_even_realities_g1_double_tap"
|
||||
android:icon="@drawable/ic_touch_double"
|
||||
android:key="pref_even_realities_g1_touch_double_right"
|
||||
android:summary="@string/pref_even_realities_g1_touch_double_right_summary"
|
||||
android:title="@string/pref_even_realities_g1_touch_double_right_title" />
|
||||
|
||||
<ListPreference
|
||||
android:title="@string/pref_even_realities_g1_touch_long_left_title"
|
||||
android:summary="@string/pref_even_realities_g1_touch_long_left_summary"
|
||||
android:defaultValue="@string/menuitem_nothing"
|
||||
android:entries="@array/pref_device_even_realities_g1_double_tap"
|
||||
android:entryValues="@array/pref_device_even_realities_g1_double_tap"
|
||||
android:key="pref_even_realities_g1_touch_long_left"
|
||||
android:icon="@drawable/ic_touch_long_left"
|
||||
/>
|
||||
|
||||
<ListPreference
|
||||
android:title="@string/pref_even_realities_g1_touch_long_right_title"
|
||||
android:summary="@string/pref_even_realities_g1_touch_long_right_summary"
|
||||
android:defaultValue="@string/menuitem_nothing"
|
||||
android:entries="@array/pref_device_even_realities_g1_long_press"
|
||||
android:entryValues="@array/pref_device_even_realities_g1_long_press"
|
||||
android:key="pref_even_realities_g1_touch_long_right"
|
||||
android:icon="@drawable/ic_touch_long"
|
||||
/>
|
||||
</PreferenceScreen>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceScreen
|
||||
android:icon="@drawable/ic_dashboard"
|
||||
android:key="pref_screen_dashboard"
|
||||
android:persistent="false"
|
||||
android:title="@string/pref_header_dashboard"></PreferenceScreen>
|
||||
</androidx.preference.PreferenceScreen>
|
||||
@@ -1,9 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<PreferenceScreen
|
||||
android:icon="@drawable/ic_watch"
|
||||
android:icon="@drawable/ic_device_display"
|
||||
android:key="pref_screen_display"
|
||||
android:persistent="false"
|
||||
android:title="@string/pref_header_display">
|
||||
</PreferenceScreen>
|
||||
android:title="@string/pref_header_display"></PreferenceScreen>
|
||||
</androidx.preference.PreferenceScreen>
|
||||
|
||||
Reference in New Issue
Block a user